Tablecloth Management System

Technology: tkinter, json, streamlit


Tablecloth Management System

I was tasked with developing a system that could keep track of tablecloths as they were being requested and rented out. My system is split into two parts:

  • The website, which keeps track of the orders.
  • The Raspberry Pi system, which uses a barcode scanner to read each order, display it, and update the inventory of all tablecloths.

Website Code

import streamlit as st
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
import io
import barcode
from barcode.writer import ImageWriter
import json
import os

Explanation:

These are the necessary imports for the website.

  • streamlit builds the web interface.
  • smtplib and the email modules send emails with attachments.
  • barcode and ImageWriter generate barcodes for each request.
  • json, os, and io handle file operations and in-memory image data.
EMAIL_SENDER = "oribadu13@gmail.com"
EMAIL_PASSWORD = "**********************"
ADMIN_EMAIL = "oribadu13@gmail.com"
REQUESTS_FILE = "requests.json"

Explanation:

These constants define:

  • The sender’s Gmail credentials for sending emails.
  • The admin email for internal notifications.
  • The filename where all requests will be stored.
# Load and update request IDs
def load_requests():
    if os.path.exists(REQUESTS_FILE):
        with open(REQUESTS_FILE, "r") as f:
            return json.load(f)
    return {}

def save_requests(requests):
    with open(REQUESTS_FILE, "w") as f:
        json.dump(requests, f, indent=4)

def generate_next_request_id(requests):
    if not requests:
        return "REQ-100001"
    max_id = max(int(rid.split("-")[1]) for rid in requests.keys())
    return f"REQ-{max_id + 1}"

Explanation:

  • load_requests() reads existing data from requests.json.
  • save_requests() writes all requests back to the file.
  • generate_next_request_id() creates a new unique ID (e.g., REQ-100001, REQ-100002, etc.) by checking existing keys.
# Email function
def send_email(recipient, subject, body, barcode_img_bytes):
    msg = MIMEMultipart()
    msg['From'] = EMAIL_SENDER
    msg['To'] = recipient
    msg['Subject'] = subject
    msg.attach(MIMEText(body, 'plain'))

if barcode_img_bytes:
        barcode_img_bytes.seek(0)
        img = MIMEImage(barcode_img_bytes.read(), name="request_barcode.png")
        msg.attach(img)

with smtplib.SMTP("smtp.gmail.com", 587) as server:
        server.starttls()
        server.login(EMAIL_SENDER, EMAIL_PASSWORD)
        server.sendmail(EMAIL_SENDER, recipient, msg.as_string())

Explanation:

This function composes and sends an email with a barcode image attachment.

It creates a MIME email, attaches text and an image, and connects to Gmail’s SMTP server (smtp.gmail.com port 587).

# Streamlit UI
st.title("Tablecloth Request Form")

requests_data = load_requests()  # Load existing requests

Explanation:

This sets up the main Streamlit page title and loads any existing requests into memory when the app starts.

with st.form("request_form"):
    name = st.text_input("Your Name")
    department = st.text_input("Department")
    email = st.text_input("Email")
    event = st.text_input("Event Name")

st.subheader("Request Tablecloths (Max 2 Types)")
    selections = []

# Allow up to 2 types per request
    for i in range(1, 3):
        st.markdown(f"### Item {i}")
        shape = st.selectbox(f"Shape (Item {i})", ["Round", "Square"], key=f"shape_{i}")
        color = st.selectbox(f"Color (Item {i})", ["Blue", "Black"], key=f"color_{i}")
        quantity = st.number_input(f"Quantity (Item {i})", min_value=0, max_value=20, step=1, key=f"qty_{i}")
        if quantity > 0:
            selections.append({"color": color, "shape": shape, "quantity": quantity})

submit = st.form_submit_button("Submit Request")

Explanation:

This builds the web form interface:

  • Takes user information (name, department, email, event).
  • Lets the user select up to two types of tablecloths.
  • Each type has shape, color, and quantity (up to 20).
  • All chosen items are stored in selections.
    if submit:
        if not selections:
            st.error("Please select at least one tablecloth item.")
        else:
            request_id = generate_next_request_id(requests_data)

# Save request to requests.json
            requests_data[request_id] = selections
            save_requests(requests_data)

# Generate barcode
            code128 = barcode.get('code128', request_id, writer=ImageWriter())
            barcode_bytes = io.BytesIO()
            code128.write(barcode_bytes)

Explanation:

When the form is submitted:

  • It validates that at least one item is selected.
  • A new request_id is generated.
  • The request details are saved into requests.json.
  • A Code128 barcode is created for the request ID and stored in memory (barcode_bytes).
            # Email content
            linens_summary = "\n".join([f"{item['quantity']} {item['color']} {item['shape']}" for item in selections])

customer_msg = f"""
            Hi {name},

Your tablecloth request has been received.
            Request ID: {request_id}
            Event: {event}
            Linens Requested:
            {linens_summary}
            """

admin_msg = f"""
            NEW REQUEST RECEIVED

Name: {name}
            Department: {department}
            Email: {email}
            Event: {event}
            Request ID: {request_id}
            Linens Requested:
            {linens_summary}
            """

send_email(email, "Your Tablecloth Request", customer_msg, barcode_bytes)
            send_email(ADMIN_EMAIL, f"New Tablecloth Request: {request_id}", admin_msg, barcode_bytes)

st.success(f"✅ Request submitted successfully! Request ID: {request_id}")

Explanation:

  • Summarizes the requested linens (quantity, color, shape).
  • Builds two email messages: one for the customer and one for the admin.
  • Attaches the barcode image to both.
  • Displays a success message with the unique Request ID.

Raspberry Pi Code

import tkinter as tk
from tkinter import messagebox, ttk
import json
import os

Explanation:

These imports are used for building the Pi’s graphical interface (tkinter), showing pop-ups (messagebox), and managing structured data (json, os).

INVENTORY_FILE = "inventory.json"
REQUESTS_FILE = "requests.json"  # Store incoming requests linked to barcodes

Explanation:

Two files are used locally on the Raspberry Pi:

  • inventory.json tracks all tablecloths and their statuses.
  • requests.json holds customer requests synced from the website.
# Inventory Handling
def load_json(file):
    if os.path.exists(file):
        with open(file, "r") as f:
            return json.load(f)
    return []

def save_json(file, data):
    with open(file, "w") as f:
        json.dump(data, f, indent=4)

Explanation:

Reusable functions for reading and writing JSON files. If a file doesn’t exist, it returns an empty list.

# Load inventory and requests
inventory = load_json(INVENTORY_FILE)
requests = load_json(REQUESTS_FILE)

Explanation:

These lines initialize the app by loading both JSON files into memory.

def register_tablecloth():
    barcode = entry_barcode.get().strip()
    color = color_var.get()

if any(item['barcode'] == barcode for item in inventory):
        messagebox.showwarning("Duplicate", "This barcode is already registered.")
    else:
        inventory.append({"barcode": barcode, "color": color, "status": "Available"})
        save_json(INVENTORY_FILE, inventory)
        update_inventory_list()
        entry_barcode.delete(0, tk.END)
        messagebox.showinfo("Registered", f"Tablecloth {barcode} registered successfully.")

Explanation:

Registers a new tablecloth:

  • Checks for duplicates.
  • Adds a new record with default status “Available.”
  • Updates the inventory table and confirms the registration.
def check_action(action):
    barcode = entry_action_barcode.get().strip()
    match = next((item for item in inventory if item['barcode'] == barcode), None)

if not match:
        messagebox.showerror("Error", "Tablecloth not registered.")
    else:
        if action == "Check Out" and match["status"] == "Available":
            match["status"] = "Checked Out"
        elif action == "Check In" and match["status"] in ["Checked Out", "Out for Cleaning"]:
            match["status"] = "Available"
        elif action == "Out for Cleaning" and match["status"] == "Checked Out":
            match["status"] = "Out for Cleaning"
        else:
            messagebox.showwarning("Invalid", "Action not valid for current status.")
            return

save_json(INVENTORY_FILE, inventory)
        update_inventory_list()
        entry_action_barcode.delete(0, tk.END)
        messagebox.showinfo("Updated", f"{barcode} marked as {action}.")

Explanation:

Handles the actions of checking in, checking out, and marking tablecloths as “Out for Cleaning.” It validates current status transitions to prevent logical errors.

def update_inventory_list():
    for row in tree.get_children():
        tree.delete(row)
    for item in inventory:
        tree.insert("", "end", values=(item["barcode"], item["color"], item["status"]))

Explanation:

Clears and repopulates the inventory display table whenever the data changes.

def scan_request_barcode():
    req_id = entry_request_barcode.get().strip()
    if req_id in requests:
        requested_items = requests[req_id]
        display_requested_items(requested_items)
    else:
        messagebox.showerror("Not Found", "Request ID not found.")

Explanation:

Looks up a Request ID (barcode) from the customer request file and shows the requested items if found.

def display_requested_items(items):
    for row in tree_request.get_children():
        tree_request.delete(row)
    for linen in items:
        tree_request.insert("", "end", values=(linen["color"], linen["quantity"]))
    messagebox.showinfo("Request Loaded", "Now scan each item to check them out.")

Explanation:

Displays the customer’s requested items in a small list box so the admin can prepare them.

# --------- GUI Setup ---------
root = tk.Tk()
root.title("Tablecloth Inventory System")
root.geometry("750x600")

Explanation:

Initializes the main Tkinter window with a title and dimensions.

# Load inventory to UI
update_inventory_list()

root.mainloop()

Explanation:

Loads current inventory data into the table at startup, then starts the Tkinter event loop to keep the app running.

Summary:

The website handles customer input, barcode creation, and emails. The Raspberry Pi app manages the physical inventory with real-time barcode scanning and updates to inventory.json. Together, these create a complete closed-loop system for tracking, checking out, and returning tablecloths.