DEV Community

Gideon Kiprono
Gideon Kiprono

Posted on

KIOSK_MANAGER : End to end python project

This is a write-up on how we built a real stock-and-sales system for a Nairobi kiosk using only the Python basics: variables, conditionals, loops, functions, data structures, strings and files

The problem

A small kiosk in Nairobi tracks everything in a notebook: how many loaves of bread are left, what was sold today, how much money came in. Notebooks get lost, pages get smudged, and adding up a day's sales by hand is slow and error-prone.

Our group was asked to replace the notebook with a program. The owner needs to:

  • See what is in stock.
  • Restock items or add new ones.
  • Sell products and record each sale.
  • See a sales report.
  • Search for a product.
  • Keep everything saved between days.

The twist is that we could only use what we had learned in six sessions of Python: no classes, no try/except, no csv module. That constraint makes it a great end-to-end project, because every basic skill has to earn its place.

Project Scope

Skill How it shows up in Kiosk Manager
Basics input() prompts, correct data types, f-string output
Conditionals Is there enough stock? Is the menu choice valid?
Loops A while True menu; for loops over sales and stock
Functions Every feature is its own function, called from a clean main()
Data structures Dictionary for stock, list of tuples for sales, set of unique items sold
Strings and files Cleaning input with .strip(), .title(), .lower(); saving and loading .txt files

Step 1: Design the data

Before writing any code, we decided how the data would look. Good structure makes everything after it easier.

Stock is a dictionary of dictionaries. The product name is the key, and the value holds price and quantity:

inventory initialization

Looking up inventory["Pen"]["quantity"] is instant and readable, and checking whether a product exists is just name in inventory.

Sales are a list of tuples. Each sale never changes after it happens, which is exactly what a tuple is for:

sales log

Unique products sold are a set. A set ignores duplicates, so adding "Pen" twice still counts it once:

sold-items

Step 2: Welcome and setup

The program opens by asking for the kiosk's name and the owner's name. Raw input() is never trusted: .strip() removes stray spaces and .title() fixes the capitalisation, so " navas herbert " becomes "Navas Herbert".

We split this into two small functions. One collects the information and returns two values; the other prints the banner.

welcome

The file names sit at the top as constants. If we ever rename a file, we change it in one place.

Step 3: Saving and loading data

Data that vanishes when the program closes is useless to a shopkeeper, so we used plain text files with one comma-separated record per line. This is what Dictionary.txt looks like:

dictionary

On startup, os.path.exists() decides whether to load that file or begin with a small default inventory. Each line is turned back into a dictionary entry with .split(",").

to come back here!!!!

Saving uses three small functions, and the file mode matters:

saving

  • Mode "w" overwrites, which is right for inventory because the file should always hold today's stock.

  • Mode "a" appends, which is right for sales because yesterday's sales must never be erased.

Step 4: Viewing stock

Aligned columns come from f-string padding: {product:<15} left-aligns a value in a 15-character column.

View stock

Step 5: Adding or restocking a product

One function handles both jobs. If the product exists, we add units to it; if not, we ask for a price and a starting quantity. Every input is checked with .isdigit() before converting with int(), so typing letters can never crash the program.

# ================function to add/ restock a product========================
def add_or_restock_product():

    product = input("Enter product name: ").strip().title()

    # Validate product name
    if product == "" or product.isdigit():
        print("Invalid product name. Please enter a valid product name.")
        return

    # Product already exists
    if product in inventory:

        quantity = input("Enter quantity to add: ").strip()

        if not quantity.isdigit():
            print("Quantity must be a number.")
            return

        quantity = int(quantity)

        if quantity <= 0:
            print("Quantity must be greater than 0.")
            return

        inventory[product]["quantity"] += quantity

        save_inventory()

        print(f"{product} has been restocked successfully.")

    # Brand-new product
    else:

        price = input("Enter product price: ").strip()
        quantity = input("Enter product quantity: ").strip()

        if not price.isdigit() or not quantity.isdigit():
            print("Price and quantity must be numbers.")
            return

        price = int(price)
        quantity = int(quantity)

        if price <= 0 or quantity <= 0:
            print("Price and quantity must be greater than 0.")
            return

        inventory[product] = {
            "price": price,
            "quantity": quantity
        }

        save_inventory()

        print(f"{product} has been added successfully.")
Enter fullscreen mode Exit fullscreen mode

The repeated return statements are _"guard clause_s": the moment something is wrong, the function stops. The successful path stays flat and easy to read.

Step 6: Selling a product

This is where the business logic lives. A sale only goes through if the product exists, the quantity is a positive whole number, and there is enough stock.

#=============== Function to Sell a Product================
def sell_product():
    product = input('Which product to sell: ').strip().title()

    # Validate product name
    if product == "" or product.isdigit():
        print("Invalid product name. Please enter a valid product name.")
        return
    if product not in inventory:
        print(f"{product} is not available in the inventory.")
        return

    if product in inventory:
        quantity = input('Enter the units to be sold: ').strip()

        if not quantity.isdigit():
            print('Quantity must be a digit')
            return
        quantity = int(quantity)

        if quantity <= 0:
            print('Quantity should be greater than 0')
            return

        if quantity > inventory[product]["quantity"]:
            print(
            f"Not enough stock. Only "
            f"{inventory[product]['quantity']} units available.")
            return

        price = inventory[product]['price']
        total = quantity * price

        inventory[product]["quantity"] -= quantity

        sale = (product, quantity, total)

        sales_log.append(sale)

        sold_products.add(product)

        save_inventory()
        save_sales_log()
        save_sold_products()

        print(f"Sold {quantity} units of {product} sucessfuly for {total}")

    print("Sale successful.")
    print(f"Product: {product}")
    print(f"Quantity: {quantity}")
    print(f"Total: KES {total}")
Enter fullscreen mode Exit fullscreen mode

Nothing changes until every check has passed. That is why a refused sale never leaves the stock half-updated.

Step 7: The sales report

One loop does three jobs:
- it prints each sale,
- adds to a revenue accumulator,
- builds a dictionary of units sold per product.
After the loop, max() picks the best seller.

#===============Function to print the sales report==============================

def sales_report():
    if not sales_log:
        print("No sales yet this session.")
        return

    print("\nSALES REPORT")
    print("-" * 40)
    print(f"{'Product':<15}{'Quantity':<10}{'Total (KES)':<12}")
    print("-" * 40)

    revenue = 0
    units_by_product = {}
    for product, quantity, total in sales_log:
        print(f"{product:<15}{quantity:<10}{total:<12}")
        revenue += total
        units_by_product[product] = units_by_product.get(product, 0) + quantity

    best = max(units_by_product, key=units_by_product.get)
    print("-" * 40)
    print(f"Total revenue: KES {revenue}")
    print(f"Unique products sold: {len(sold_products)}")
    print(f"Best seller: {best} ({units_by_product[best]} units)\n")
Enter fullscreen mode Exit fullscreen mode

.get(product, 0) returns 0 the first time a product appears, which avoids a missing-key error. We define "best seller" as most units sold, and stay consistent with that.

Step 8: Searching products

A forgiving search lowercases both the search term and the product name, then uses the in operator, so typing calc finds Calculator.

# ===============Function to search products====================
def search_products():
    term = input("Search for a product: ").strip().lower()
    if term == "":
        print("Please type something to search for.")
        return

    found = False
    for product, details in inventory.items():
        if term in product.lower():
            print(f"{product:<15}KES {details['price']:<8}Qty: {details['quantity']}")
            found = True
    if not found:
        print("No matches found.")
Enter fullscreen mode Exit fullscreen mode

The found flag is a classic pattern: assume nothing matched, flip it to True on the first match, and print "No matches found" only if it never flipped.

Step 9: The main program

Because every feature is its own function, the main flow reads almost like plain English: ask, welcome, load, then loop until the user exits.

kiosk_name, owner_name = ask_kiosk_info()
show_owner_details(kiosk_name, owner_name)
print()

inventory = load_inventory()
sales_log = []
sold_products = set()

while True:
    print('==============MAIN MENU============== ')
    print('1. View Stock')
    print('2. Add/ Restock a Product')
    print('3. Sell a Product')
    print('4. View Sales Report')
    print('5. Search Products')
    print('6. Exit')

    choice = input('Enter your choice (Between 1 and 6): ').strip()

    if not choice.isdigit():
        print('Invalid choice. Please enter a number between 1 and 6')
        continue
    choice = int(choice)
    if choice < 1 or choice > 6:
        print('Invalid choice. Please enter a number between 1 and 6')
        continue

    if choice == 1:
        view_inventory()
    elif choice == 2:
        add_or_restock_product()
    elif choice == 3:
        sell_product()
    elif choice == 4:
        sales_report()
    elif choice == 5:
        search_products()
    else:
        save_inventory()
        save_sales_log()
        save_sold_products()
        print(f'Exiting. Goodbye, {owner_name}!')
        break
Enter fullscreen mode Exit fullscreen mode

The menu choice is validated in two steps: .isdigit() first (is it a number at all?), then a range check (is it 1 to 6?). continue sends the loop straight back to the menu instead of crashing.

Output 1

Output 2

Conclusion

Kiosk Manager is small, but it is a complete program: it takes input, applies business rules, stores data, reports results and survives a restart. That is the full loop of real software, and if every group member can explain every function above, we built something a real shopkeeper could use tomorrow morning.

Top comments (1)

Collapse
 
sreno77 profile image
Scott Reno •

This is a good start. You might want to look into sqlite so that you can save all of your data to a local database.