Build a Medical Store App in Python OOP — Step-by-Step (with Full Code)

Learn how to manage medicines, stock, and customers with clean, beginner-friendly Object-Oriented Python.

Summary: In this tutorial, you’ll build a simple yet practical Medical Store App using Python OOP. You’ll create classes for Medicine, Customer, and MedicalStore, connect them with clear methods, and finish with a fully working script you can run immediately. Perfect for beginners who want a hands-on OOP project.


Table of Contents

  1. Why OOP for a Medical Store?
  2. What You Need
  3. Design the Data Model (Classes)
  4. Quick Start: Minimal Working Example
  5. Step-by-Step Build
  6. Full Copy-Paste Code
  7. How to Run
  8. Next Steps & Enhancements
  9. FAQ

1) Why OOP for a Medical Store?

Object-Oriented Programming maps naturally to the real world. In a pharmacy, you deal with:

  • Medicines — items with a name, price, and stock quantity.
  • Customers — people who buy medicines and have purchase history.
  • Store — a system that manages inventory and sales.

By turning each concept into a class, your code becomes cleaner, reusable, and easy to extend later (e.g., adding expiry dates, invoices, GUI, or a web API).

2) What You Need

  • Python 3.8+ installed (download).
  • A code editor (VS Code, PyCharm, or any text editor).
  • Basic familiarity with running Python files.

3) Design the Data Model (Classes)

We’ll use three core classes:

  • Medicine: name, price, quantity; methods to restock and format output.
  • Customer: name, purchase list; methods to record purchases and compute totals.
  • MedicalStore: a dictionary of medicines; methods to add/show stock and sell items.

4) Quick Start: Minimal Working Example

If you want the fastest path to results, copy this snippet first. It creates a store, adds stock, sells items, and prints results.

class Medicine:
    def __init__(self, name: str, price: float, quantity: int):
        self.name = name
        self.price = float(price)
        self.quantity = int(quantity)

    def restock(self, amount: int):
        self.quantity += int(amount)

    def __str__(self):
        return f"{self.name} - ${self.price:.2f} (Stock: {self.quantity})"


class Customer:
    def __init__(self, name: str):
        self.name = name
        self.purchases = []  # list of (medicine_name, qty, line_total)

    def add_purchase(self, med_name: str, qty: int, line_total: float):
        self.purchases.append((med_name, qty, round(line_total, 2)))

    def total_spent(self) -> float:
        return round(sum(p[2] for p in self.purchases), 2)

    def __str__(self):
        return f"Customer: {self.name}, Purchases: {len(self.purchases)}"


class MedicalStore:
    def __init__(self, name: str):
        self.name = name
        self.medicines = {}  # {name: Medicine}

    def add_medicine(self, medicine: Medicine):
        self.medicines[medicine.name] = medicine

    def show_stock(self):
        print(f"--- {self.name} Stock ---")
        if not self.medicines:
            print("(no items yet)")
        for med in self.medicines.values():
            print(med)

    def sell_medicine(self, customer: Customer, med_name: str, qty: int):
        if med_name not in self.medicines:
            print(f"❌ {med_name} not available")
            return False
        med = self.medicines[med_name]
        if qty <= 0:
            print("❌ Quantity must be positive")
            return False
        if med.quantity < qty:
            print(f"❌ Not enough stock for {med_name} (have {med.quantity})")
            return False
        med.quantity -= qty
        line_total = med.price * qty
        customer.add_purchase(med_name, qty, line_total)
        print(f"✅ Sold {qty} x {med_name} to {customer.name} (${line_total:.2f})")
        return True


# Demo run
if __name__ == "__main__":
    store = MedicalStore("HealthPlus Pharmacy")
    store.add_medicine(Medicine("Paracetamol", 2.5, 100))
    store.add_medicine(Medicine("Amoxicillin", 5.0, 50))
    store.add_medicine(Medicine("Vitamin C", 1.0, 200))

    store.show_stock()

    john = Customer("John Doe")
    store.sell_medicine(john, "Paracetamol", 5)
    store.sell_medicine(john, "Vitamin C", 10)

    print(f"Total spent by {john.name}: ${john.total_spent():.2f}")
    store.show_stock()

5) Step-by-Step Build

5.1) Create the Medicine class

Each medicine has a name, price, and stock quantity. Add a restock() helper to increase inventory safely.

class Medicine:
    def __init__(self, name: str, price: float, quantity: int):
        self.name = name
        self.price = float(price)
        self.quantity = int(quantity)

    def restock(self, amount: int):
        if amount <= 0:
            raise ValueError("Restock amount must be positive")
        self.quantity += int(amount)

    def __str__(self):
        return f"{self.name} - ${self.price:.2f} (Stock: {self.quantity})"

5.2) Create the Customer class

Customers keep a simple purchase history that stores what they bought, how many, and the line total.

class Customer:
    def __init__(self, name: str):
        self.name = name
        self.purchases = []  # list[tuple[str, int, float]]

    def add_purchase(self, med_name: str, qty: int, line_total: float):
        self.purchases.append((med_name, int(qty), float(line_total)))

    def total_spent(self) -> float:
        return round(sum(total for _, _, total in self.purchases), 2)

    def __str__(self):
        return f"Customer: {self.name}, Purchases: {len(self.purchases)}"

5.3) Create the MedicalStore class

The store manages the catalog (dictionary by name), shows stock, and handles sales with validation.

class MedicalStore:
    def __init__(self, name: str):
        self.name = name
        self.medicines = {}

    def add_medicine(self, medicine: Medicine):
        self.medicines[medicine.name] = medicine

    def show_stock(self):
        print(f"--- {self.name} Stock ---")
        if not self.medicines:
            print("(no items yet)")
        for med in self.medicines.values():
            print(med)

    def sell_medicine(self, customer: Customer, med_name: str, qty: int):
        if med_name not in self.medicines:
            print(f"❌ {med_name} not available")
            return False
        med = self.medicines[med_name]
        if qty <= 0:
            print("❌ Quantity must be positive")
            return False
        if med.quantity < qty:
            print(f"❌ Not enough stock for {med_name} (have {med.quantity})")
            return False
        med.quantity -= qty
        line_total = med.price * qty
        customer.add_purchase(med.name, qty, line_total)
        print(f"✅ Sold {qty} x {med.name} to {customer.name} (${line_total:.2f})")
        return True

5.4) Wire it up (demo)

if __name__ == "__main__":
    store = MedicalStore("HealthPlus Pharmacy")
    store.add_medicine(Medicine("Paracetamol", 2.50, 100))
    store.add_medicine(Medicine("Ibuprofen", 3.00, 80))
    store.add_medicine(Medicine("Vitamin C", 1.00, 200))

    store.show_stock()

    alice = Customer("Alice")
    store.sell_medicine(alice, "Ibuprofen", 3)
    store.sell_medicine(alice, "Vitamin C", 12)

    print(f"Total spent by {alice.name}: ${alice.total_spent():.2f}")
    store.show_stock()

6) Full Copy-Paste Code (Single File)

Save the following as medical_store.py:

"""
Medical Store App (Python OOP)
- Manage medicines (name, price, stock)
- Record customer purchases
- Sell with validation and auto stock updates
"""

from typing import Dict, List, Tuple


class Medicine:
    def __init__(self, name: str, price: float, quantity: int):
        if price < 0:
            raise ValueError("Price cannot be negative")
        if quantity < 0:
            raise ValueError("Quantity cannot be negative")
        self.name = name.strip()
        self.price = float(price)
        self.quantity = int(quantity)

    def restock(self, amount: int):
        if amount <= 0:
            raise ValueError("Restock amount must be positive")
        self.quantity += int(amount)

    def __str__(self) -> str:
        return f"{self.name} - ${self.price:.2f} (Stock: {self.quantity})"


class Customer:
    def __init__(self, name: str):
        self.name = name.strip()
        self.purchases: List[Tuple[str, int, float]] = []

    def add_purchase(self, med_name: str, qty: int, line_total: float):
        self.purchases.append((med_name, int(qty), round(float(line_total), 2)))

    def total_spent(self) -> float:
        return round(sum(total for _, _, total in self.purchases), 2)

    def receipt(self) -> str:
        if not self.purchases:
            return f"{self.name} — no purchases yet."
        lines = [f"Receipt for {self.name}:"]
        for med, qty, total in self.purchases:
            lines.append(f"  - {med} x{qty}: ${total:.2f}")
        lines.append(f"TOTAL: ${self.total_spent():.2f}")
        return "\n".join(lines)

    def __str__(self) -> str:
        return f"Customer: {self.name}, Purchases: {len(self.purchases)}"


class MedicalStore:
    def __init__(self, name: str):
        self.name = name.strip()
        self.medicines: Dict[str, Medicine] = {}

    def add_medicine(self, medicine: Medicine):
        """Add or replace a medicine by name."""
        self.medicines[medicine.name] = medicine

    def show_stock(self):
        print(f"--- {self.name} Stock ---")
        if not self.medicines:
            print("(no items yet)")
            return
        for med in self.medicines.values():
            print(med)

    def find(self, med_name: str) -> Medicine | None:
        return self.medicines.get(med_name)

    def sell_medicine(self, customer: Customer, med_name: str, qty: int) -> bool:
        med = self.find(med_name)
        if not med:
            print(f"❌ {med_name} not available")
            return False
        if qty <= 0:
            print("❌ Quantity must be positive")
            return False
        if med.quantity < qty:
            print(f"❌ Not enough stock for {med.name} (have {med.quantity})")
            return False
        med.quantity -= qty
        line_total = med.price * qty
        customer.add_purchase(med.name, qty, line_total)
        print(f"✅ Sold {qty} x {med.name} to {customer.name} (${line_total:.2f})")
        return True


def demo():
    store = MedicalStore("HealthPlus Pharmacy")
    store.add_medicine(Medicine("Paracetamol", 2.50, 100))
    store.add_medicine(Medicine("Ibuprofen", 3.00, 80))
    store.add_medicine(Medicine("Vitamin C", 1.00, 200))
    store.add_medicine(Medicine("Amoxicillin", 5.00, 50))

    store.show_stock()

    john = Customer("John Doe")
    store.sell_medicine(john, "Paracetamol", 5)
    store.sell_medicine(john, "Vitamin C", 10)
    store.sell_medicine(john, "Ibuprofen", 3)

    print()
    print(john.receipt())
    print()
    store.show_stock()


if __name__ == "__main__":
    demo()

7) How to Run

  1. Copy the full code above into a file named medical_store.py.
  2. Open a terminal in the same folder.
  3. Run: python3 medical_store.py
  4. Check the console output for stock, sales, and the customer receipt.

8) Next Steps & Enhancements

  • Persistence: Save/load stock and purchases using json or sqlite3.
  • Expiry & Batches: Track expiry dates and batch numbers per medicine.
  • Discounts/Taxes: Add coupon logic and tax calculation in sell_medicine().
  • Receipts: Export a receipt to .txt or .pdf for each sale.
  • GUI: Use Tkinter for a desktop app (buttons for add/sell/show).
  • Web App: Convert into Flask/Django routes (inventory page, sell endpoint).

Bonus: Tiny JSON persistence example

import json

def save_stock(store: MedicalStore, path="stock.json"):
    data = [
        {"name": m.name, "price": m.price, "quantity": m.quantity}
        for m in store.medicines.values()
    ]
    with open(path, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2)

def load_stock(store: MedicalStore, path="stock.json"):
    try:
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
        for item in data:
            store.add_medicine(Medicine(item["name"], item["price"], item["quantity"]))
    except FileNotFoundError:
        pass

9) FAQ

Q: Can I add input prompts to sell interactively?
A: Yes! Wrap input() in a simple loop that asks for a medicine name and quantity, then calls sell_medicine().

Q: How do I prevent negative prices or invalid quantities?
A: Validation is already included in the Medicine constructor and sell_medicine(). Extend as needed (e.g., min/max price caps).

Q: Can I support multiple stores?
A: Absolutely. Instantiate multiple MedicalStore objects and persist each store’s catalog separately.



Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *