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.") 

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}") 

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") 

.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.") 

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 

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.