This is the first version of a restaurant billing project built with Python and Tkinter. The menu is stored in a Python dictionary. Each menu item has a quantity selector, and the selected items are added to a Treeview when the user generates the bill.
The program calculates the subtotal, adds a sample 10% tax and displays the final amount. The Reset button clears the invoice and prepares the interface for the next order.
Menu dictionary
|
v
Quantity selection
|
v
Generate Bill
|
+--> Treeview invoice rows
+--> Subtotal
+--> Tax
+--> Final total
|
v
Reset
Show Table of Contents
The first version does not need a database. Each menu item can be stored in a dictionary.
menu={
0:{'name':'Item One','price':Decimal('20.00')},
1:{'name':'Item Two','price':Decimal('40.00')},
2:{'name':'Item Three','price':Decimal('30.00')}
}
Each dictionary entry contains:
name: text shown to the user.price: price for one unit.Using named fields such as name and price makes the data easier to understand than relying on positions such as menu[i][0] and menu[i][1].
Python Decimal provides fixed decimal arithmetic that is more suitable for currency-style calculations than binary floating-point values.
from decimal import Decimal
price=Decimal('20.00')
The window uses two main sections:
root=tk.Tk()
root.geometry('1000x650')
root.title('Restaurant Billing System - plus2net')
root.columnconfigure(0,weight=3)
root.columnconfigure(1,weight=2)
root.rowconfigure(0,weight=1)
Instead of manually creating eight separate buttons and eight separate Spinboxes, we can create the controls in a loop.
quantity_vars={}
for index,item in menu.items():
row=index//4
column=index%4
card=ttk.Frame(menu_frame,padding=8)
card.grid(row=row,column=column,padx=8,pady=8,sticky='nsew')
ttk.Label(card,text=item['name']).grid(row=0,column=0)
ttk.Label(card,text=f"Price: {item['price']:.2f}").grid(row=1,column=0)
quantity_vars[index]=tk.IntVar(value=0)
spin=tk.Spinbox(
card,
from_=0,
to=10,
width=4,
textvariable=quantity_vars[index],
state='readonly'
)
spin.grid(row=2,column=0,pady=5)
This design is easier to maintain. Adding another menu item to the dictionary automatically adds another control to the interface.
The invoice is flat tabular data, so we can use named Treeview columns.
tree=ttk.Treeview(
bill_frame,
columns=('item','price','qty','total'),
show='headings',
height=12
)
The columns represent:
Item | Price | Quantity | Total
The final column contains:
quantity * unit price
When the user clicks Get Bill, the program first removes any previously generated invoice rows.
for row in tree.get_children():
tree.delete(row)
Then it checks every menu quantity:
for index,item in menu.items():
qty=quantity_vars[index].get()
if qty>0:
line_total=(item['price']*qty).quantize(MONEY)
subtotal+=line_total
Only ordered items are added to Treeview.
tree.insert(
'',
tk.END,
values=(
item['name'],
f"{item['price']:.2f}",
qty,
f"{line_total:.2f}"
)
)
This tutorial uses a sample 10% tax rate.
TAX_RATE=Decimal('0.10')
tax=(subtotal*TAX_RATE).quantize(MONEY)
final_total=(subtotal+tax).quantize(MONEY)
The older version created new IntVar objects and reconfigured every Spinbox during reset. That is unnecessary.
Keep the original variables and set them back to zero:
for qty_var in quantity_vars.values():
qty_var.set(0)
Then clear the Treeview and totals:
subtotal_var.set('0.00')
tax_var.set('0.00')
total_var.set('0.00')
import tkinter as tk
from tkinter import ttk
from decimal import Decimal, ROUND_HALF_UP
MONEY=Decimal('0.01')
TAX_RATE=Decimal('0.10')
menu={
0:{'name':'Item One','price':Decimal('20.00')},
1:{'name':'Item Two','price':Decimal('40.00')},
2:{'name':'Item Three','price':Decimal('30.00')},
3:{'name':'Item Four','price':Decimal('20.00')},
4:{'name':'Item Five','price':Decimal('30.00')},
5:{'name':'Item Six','price':Decimal('20.00')},
6:{'name':'Item Seven','price':Decimal('40.00')},
7:{'name':'Item Eight','price':Decimal('30.00')}
}
root=tk.Tk()
root.geometry('1000x650')
root.minsize(850,550)
root.title('Restaurant Billing System - plus2net')
root.columnconfigure(0,weight=3)
root.columnconfigure(1,weight=2)
root.rowconfigure(0,weight=1)
menu_frame=ttk.Frame(root,padding=10)
menu_frame.grid(row=0,column=0,sticky='nsew')
bill_frame=ttk.Frame(root,padding=10)
bill_frame.grid(row=0,column=1,sticky='nsew')
bill_frame.rowconfigure(1,weight=1)
bill_frame.columnconfigure(0,weight=1)
for column in range(4):
menu_frame.columnconfigure(column,weight=1)
ttk.Label(menu_frame,text='Menu',font=('Times',22,'bold')).grid(row=0,column=0,columnspan=4,pady=(0,12))
quantity_vars={}
for index,item in menu.items():
row=index//4+1
column=index%4
card=ttk.Frame(menu_frame,padding=8,relief='ridge')
card.grid(row=row,column=column,padx=8,pady=8,sticky='nsew')
ttk.Label(card,text=item['name'],font=('Times',13,'bold')).grid(row=0,column=0,pady=4)
ttk.Label(card,text=f"Price: {item['price']:.2f}").grid(row=1,column=0,pady=4)
quantity_vars[index]=tk.IntVar(value=0)
spin=tk.Spinbox(card,from_=0,to=10,width=4,textvariable=quantity_vars[index],state='readonly')
spin.grid(row=2,column=0,pady=6)
button_frame=ttk.Frame(menu_frame)
button_frame.grid(row=3,column=0,columnspan=4,pady=15)
ttk.Label(bill_frame,text='Bill',font=('Times',22,'bold')).grid(row=0,column=0,sticky='w',pady=(0,8))
tree=ttk.Treeview(bill_frame,columns=('item','price','qty','total'),show='headings',height=12)
tree.grid(row=1,column=0,sticky='nsew')
ys=ttk.Scrollbar(bill_frame,orient='vertical',command=tree.yview)
ys.grid(row=1,column=1,sticky='ns')
tree.configure(yscrollcommand=ys.set)
tree.heading('item',text='Item')
tree.heading('price',text='Price')
tree.heading('qty',text='Qty')
tree.heading('total',text='Total')
tree.column('item',width=140,anchor='w')
tree.column('price',width=75,anchor='e')
tree.column('qty',width=55,anchor='center')
tree.column('total',width=85,anchor='e')
subtotal_var=tk.StringVar(value='0.00')
tax_var=tk.StringVar(value='0.00')
total_var=tk.StringVar(value='0.00')
totals=ttk.Frame(bill_frame,padding=(0,12))
totals.grid(row=2,column=0,sticky='e')
ttk.Label(totals,text='Subtotal:').grid(row=0,column=0,sticky='e',padx=5)
ttk.Label(totals,textvariable=subtotal_var).grid(row=0,column=1,sticky='e')
ttk.Label(totals,text='Tax 10%:').grid(row=1,column=0,sticky='e',padx=5)
ttk.Label(totals,textvariable=tax_var).grid(row=1,column=1,sticky='e')
ttk.Label(totals,text='Final Total:',font=('Times',16,'bold')).grid(row=2,column=0,sticky='e',padx=5,pady=6)
ttk.Label(totals,textvariable=total_var,font=('Times',16,'bold')).grid(row=2,column=1,sticky='e')
def my_bill():
subtotal=Decimal('0.00')
for row in tree.get_children():
tree.delete(row)
for index,item in menu.items():
qty=quantity_vars[index].get()
if qty>0:
line_total=(item['price']*qty).quantize(MONEY,rounding=ROUND_HALF_UP)
subtotal+=line_total
tree.insert('',tk.END,values=(item['name'],f"{item['price']:.2f}",qty,f"{line_total:.2f}"))
subtotal=subtotal.quantize(MONEY,rounding=ROUND_HALF_UP)
tax=(subtotal*TAX_RATE).quantize(MONEY,rounding=ROUND_HALF_UP)
final_total=(subtotal+tax).quantize(MONEY,rounding=ROUND_HALF_UP)
subtotal_var.set(f'{subtotal:.2f}')
tax_var.set(f'{tax:.2f}')
total_var.set(f'{final_total:.2f}')
def my_reset():
for qty_var in quantity_vars.values():
qty_var.set(0)
for row in tree.get_children():
tree.delete(row)
subtotal_var.set('0.00')
tax_var.set('0.00')
total_var.set('0.00')
ttk.Button(button_frame,text='Get Bill',command=my_bill).grid(row=0,column=0,padx=8)
ttk.Button(button_frame,text='Reset',command=my_reset).grid(row=0,column=1,padx=8)
root.mainloop()
The original project contains commented image code. Images can still be added, but avoid an absolute machine-specific path such as:
G:\My Drive\testing\plus2_restaurant_v1\images\
Keep the images inside the project folder instead:
item_image=tk.PhotoImage(file='images/food-item-1.png')
Then place the image in a Label or Button:
item_button=tk.Button(card,image=item_image,text='Item One',compound='top')
PhotoImage object while it is displayed. Otherwise Tkinter may remove the image even though the widget still exists.Version 1 keeps the menu directly inside Python:
menu={
0:{'name':'Item One','price':Decimal('20.00')}
}
This is useful for understanding the GUI and billing logic first.
In Restaurant Management V-2, the product data is moved to MySQL. That allows menu items, prices, categories and availability to be maintained independently of the GUI source code.
A dictionary keeps the menu data separate from the widget code. The interface can loop through the dictionary to create menu controls automatically.
A Spinbox gives the user a controlled numeric choice and avoids typing a separate quantity for every menu item.
Treeview displays the ordered products in rows and columns, making the invoice easier to read.
Decimal provides fixed decimal arithmetic and is more appropriate for currency-style calculations than binary floating-point values.
It clears all selected quantities, removes invoice rows and sets subtotal, tax and final total back to zero.
Version 2 moves the product and price data to a database so the menu can be managed without editing the Python source.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.