π FuzzuTech CTkinter E-commerce Tools — Build Invoice & Stock Tracker in Python (GUI + SQLite)
Demo :
Click Video πππ
Features:
-
Large cover image from your YouTube thumbnail
-
Embedded YouTube Short
-
Code snippet with syntax highlighting
-
Download link for
app.py
-
Social share buttons enabled
Code :
"""
FuzzuTech: CTkinter E‑commerce Tools (Invoice + Stock Tracker)
- Modern CTkinter UI with two tabs
- SQLite backend (auto-create tables)
- Stock: add/edit/delete products
- Invoice: build invoice from stock items, auto-calc totals, save invoice + items
- Export: invoice CSV, stock CSV
Requires: customtkinter, pillow (for icons optional)
Run: python app.pyh
"""
import os
import sqlite3
import datetime as dt
import csv
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import customtkinter as ctk
APP_NAME = "FuzzuTech E‑commerce Tools"
DB_FILE = "ecom_tools.db"
# --------------------- Database Layer ---------------------
def get_conn():
conn = sqlite3.connect(DB_FILE)
conn.execute("PRAGMA foreign_keys = ON;")
return conn
def init_db():
conn = get_conn()
cur = conn.cursor()
cur.execute("""CREATE TABLE IF NOT EXISTS products(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
sku TEXT UNIQUE NOT NULL,
price REAL NOT NULL CHECK(price>=0),
qty INTEGER NOT NULL DEFAULT 0 CHECK(qty>=0),
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);""")
cur.execute("""CREATE TABLE IF NOT EXISTS invoices(
id INTEGER PRIMARY KEY AUTOINCREMENT,
customer TEXT NOT NULL,
created_at TEXT NOT NULL,
subtotal REAL NOT NULL,
tax_rate REAL NOT NULL,
tax_amount REAL NOT NULL,
total REAL NOT NULL
);""")
cur.execute("""CREATE TABLE IF NOT EXISTS invoice_items(
id INTEGER PRIMARY KEY AUTOINCREMENT,
invoice_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
qty INTEGER NOT NULL CHECK(qty>0),
unit_price REAL NOT NULL,
line_total REAL NOT NULL,
FOREIGN KEY(invoice_id) REFERENCES invoices(id) ON DELETE CASCADE,
FOREIGN KEY(product_id) REFERENCES products(id)
);""")
conn.commit()
conn.close()
# --------------------- Helpers ---------------------
def money(x):
return f"₹{x:,.2f}"
def validate_float(s, default=0.0):
try:
return float(s)
except Exception:
return float(default)
def validate_int(s, default=0):
try:
return int(s)
except Exception:
return int(default)
# --------------------- UI ---------------------
class App(ctk.CTk):
def __init__(self):
super().__init__()
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
self.title(APP_NAME)
self.geometry("1100x680")
self.minsize(980, 620)
self.style = ttk.Style(self)
self.style.theme_use("clam")
self.style.configure("Treeview", rowheight=28, font=("Inter", 12))
self.style.configure("Treeview.Heading", font=("Inter", 12, "bold"))
# Top bar
top = ctk.CTkFrame(self, corner_radius=16)
top.pack(side="top", fill="x", padx=14, pady=(12, 6))
ctk.CTkLabel(top, text=APP_NAME, font=("Inter", 22, "bold")).pack(side="left", padx=10)
ctk.CTkButton(top, text="Export Stock CSV", command=self.export_stock_csv).pack(side="right", padx=6)
ctk.CTkButton(top, text="Export Invoice CSV", command=self.export_invoice_csv).pack(side="right", padx=6)
# Tabs
self.tabs = ctk.CTkTabview(self, corner_radius=16)
self.tabs.pack(fill="both", expand=True, padx=14, pady=(6, 14))
self.tab_stock = self.tabs.add("Stock Tracker")
self.tab_invoice = self.tabs.add("Invoice Builder")
self.build_stock_tab()
self.build_invoice_tab()
self.refresh_products()
self.refresh_invoice_products()
# --------------- Stock Tab ---------------
def build_stock_tab(self):
left = ctk.CTkFrame(self.tab_stock, corner_radius=16)
left.pack(side="left", fill="y", padx=(10, 6), pady=10)
ctk.CTkLabel(left, text="Add / Update Product", font=("Inter", 18, "bold")).pack(pady=(10, 6))
self.ent_name = ctk.CTkEntry(left, placeholder_text="Product Name")
self.ent_sku = ctk.CTkEntry(left, placeholder_text="SKU (unique)")
self.ent_price = ctk.CTkEntry(left, placeholder_text="Unit Price")
self.ent_qty = ctk.CTkEntry(left, placeholder_text="Quantity")
for w in (self.ent_name, self.ent_sku, self.ent_price, self.ent_qty):
w.pack(fill="x", padx=10, pady=6)
btns = ctk.CTkFrame(left)
btns.pack(fill="x", padx=10, pady=10)
ctk.CTkButton(btns, text="Save / Update", command=self.save_product).pack(side="left", expand=True, padx=6)
ctk.CTkButton(btns, text="Clear", fg_color="#404040", command=self.clear_product_form).pack(side="left", expand=True, padx=6)
# Search
srch = ctk.CTkFrame(self.tab_stock, corner_radius=16)
srch.pack(side="top", fill="x", padx=(6,10), pady=(10,6))
ctk.CTkLabel(srch, text="Search", font=("Inter", 16, "bold")).pack(side="left", padx=10)
self.ent_search = ctk.CTkEntry(srch, placeholder_text="Name or SKU...")
self.ent_search.pack(side="left", fill="x", expand=True, padx=6)
ctk.CTkButton(srch, text="Go", command=self.search_products).pack(side="left", padx=6)
ctk.CTkButton(srch, text="Show All", fg_color="#404040", command=self.refresh_products).pack(side="left", padx=6)
# Table
table_frame = ctk.CTkFrame(self.tab_stock, corner_radius=16)
table_frame.pack(fill="both", expand=True, padx=(6,10), pady=(6,10))
self.tv_products = ttk.Treeview(table_frame, columns=("id","name","sku","price","qty"), show="headings", selectmode="browse")
for col, text, w in (("id","#",60), ("name","Name",240), ("sku","SKU",160), ("price","Price",120), ("qty","Qty",80)):
self.tv_products.heading(col, text=text)
self.tv_products.column(col, width=w, anchor="center")
self.tv_products.pack(fill="both", expand=True, side="left", padx=6, pady=6)
self.tv_products.bind("<<TreeviewSelect>>", self.on_product_select)
sb = ttk.Scrollbar(table_frame, orient="vertical", command=self.tv_products.yview)
self.tv_products.configure(yscrollcommand=sb.set)
sb.pack(side="right", fill="y")
# Row actions
row_btns = ctk.CTkFrame(self.tab_stock, corner_radius=16)
row_btns.pack(fill="x", padx=(6,10), pady=(0,10))
ctk.CTkButton(row_btns, text="Delete Selected", fg_color="#c62828", hover_color="#b71c1c", command=self.delete_product).pack(side="left", padx=6)
def save_product(self):
name = self.ent_name.get().strip()
sku = self.ent_sku.get().strip()
price = validate_float(self.ent_price.get())
qty = validate_int(self.ent_qty.get())
if not name or not sku:
messagebox.showwarning("Missing", "Name and SKU are required")
return
try:
conn = get_conn()
cur = conn.cursor()
# Upsert by SKU
cur.execute("SELECT id FROM products WHERE sku=?", (sku,))
row = cur.fetchone()
if row:
cur.execute("UPDATE products SET name=?, price=?, qty=? WHERE sku=?", (name, price, qty, sku))
else:
cur.execute("INSERT INTO products(name,sku,price,qty) VALUES(?,?,?,?)", (name, sku, price, qty))
conn.commit()
conn.close()
self.refresh_products()
self.clear_product_form()
except sqlite3.IntegrityError as e:
messagebox.showerror("Error", f"SKU must be unique.\n\n{e}")
except Exception as e:
messagebox.showerror("Error", str(e))
def clear_product_form(self):
for w in (self.ent_name, self.ent_sku, self.ent_price, self.ent_qty):
w.delete(0, "end")
def refresh_products(self):
for i in self.tv_products.get_children():
self.tv_products.delete(i)
conn = get_conn()
cur = conn.cursor()
cur.execute("SELECT id, name, sku, price, qty FROM products ORDER BY id DESC;")
for row in cur.fetchall():
self.tv_products.insert("", "end", values=(row[0], row[1], row[2], f"{row[3]:.2f}", row[4]))
conn.close()
def search_products(self):
q = self.ent_search.get().strip()
for i in self.tv_products.get_children():
self.tv_products.delete(i)
conn = get_conn()
cur = conn.cursor()
cur.execute("SELECT id, name, sku, price, qty FROM products WHERE name LIKE ? OR sku LIKE ? ORDER BY id DESC;", (f"%{q}%", f"%{q}%"))
for row in cur.fetchall():
self.tv_products.insert("", "end", values=(row[0], row[1], row[2], f"{row[3]:.2f}", row[4]))
conn.close()
def on_product_select(self, event):
sel = self.tv_products.selection()
if not sel: return
vals = self.tv_products.item(sel[0], "values")
# Prefill form
self.ent_name.delete(0,"end"); self.ent_name.insert(0, vals[1])
self.ent_sku.delete(0,"end"); self.ent_sku.insert(0, vals[2])
self.ent_price.delete(0,"end"); self.ent_price.insert(0, vals[3])
self.ent_qty.delete(0,"end"); self.ent_qty.insert(0, vals[4])
def delete_product(self):
sel = self.tv_products.selection()
if not sel:
messagebox.showinfo("Select", "Please select a product row")
return
vals = self.tv_products.item(sel[0], "values")
sku = vals[2]
if not messagebox.askyesno("Confirm", f"Delete SKU {sku}?"):
return
conn = get_conn()
cur = conn.cursor()
cur.execute("DELETE FROM products WHERE sku=?", (sku,))
conn.commit()
conn.close()
self.refresh_products()
# --------------- Invoice Tab ---------------
def build_invoice_tab(self):
top = ctk.CTkFrame(self.tab_invoice, corner_radius=16)
top.pack(fill="x", padx=10, pady=(10,6))
ctk.CTkLabel(top, text="Customer Name", font=("Inter", 14, "bold")).pack(side="left", padx=6)
self.ent_customer = ctk.CTkEntry(top, placeholder_text="e.g., Aman Khan")
self.ent_customer.pack(side="left", padx=6, fill="x", expand=True)
ctk.CTkLabel(top, text="Tax %").pack(side="left", padx=6)
self.ent_tax = ctk.CTkEntry(top, width=80)
self.ent_tax.insert(0, "18")
self.ent_tax.pack(side="left", padx=6)
ctk.CTkButton(top, text="Save Invoice", command=self.save_invoice).pack(side="right", padx=6)
middle = ctk.CTkFrame(self.tab_invoice, corner_radius=16)
middle.pack(fill="x", padx=10, pady=6)
ctk.CTkLabel(middle, text="Product").pack(side="left", padx=6)
self.cb_product = ttk.Combobox(middle, state="readonly", width=40)
self.cb_product.pack(side="left", padx=6)
ctk.CTkLabel(middle, text="Qty").pack(side="left", padx=6)
self.ent_inv_qty = ctk.CTkEntry(middle, width=90)
self.ent_inv_qty.insert(0, "1")
self.ent_inv_qty.pack(side="left", padx=6)
ctk.CTkButton(middle, text="Add Item", command=self.add_invoice_item).pack(side="left", padx=6)
table = ctk.CTkFrame(self.tab_invoice, corner_radius=16)
table.pack(fill="both", expand=True, padx=10, pady=6)
self.tv_inv = ttk.Treeview(table, columns=("name","sku","qty","price","total"), show="headings")
for col, text, w in (("name","Name",300), ("sku","SKU",140), ("qty","Qty",80), ("price","Unit Price",140), ("total","Line Total",140)):
self.tv_inv.heading(col, text=text)
self.tv_inv.column(col, width=w, anchor="center")
self.tv_inv.pack(fill="both", expand=True, side="left", padx=6, pady=6)
sb = ttk.Scrollbar(table, orient="vertical", command=self.tv_inv.yview)
self.tv_inv.configure(yscrollcommand=sb.set)
sb.pack(side="right", fill="y")
bottom = ctk.CTkFrame(self.tab_invoice, corner_radius=16)
bottom.pack(fill="x", padx=10, pady=(6,10))
self.lbl_subtotal = ctk.CTkLabel(bottom, text="Subtotal: ₹0.00", font=("Inter", 14, "bold"))
self.lbl_tax = ctk.CTkLabel(bottom, text="Tax: ₹0.00", font=("Inter", 14, "bold"))
self.lbl_total = ctk.CTkLabel(bottom, text="Total: ₹0.00", font=("Inter", 16, "bold"))
self.lbl_subtotal.pack(side="left", padx=10)
self.lbl_tax.pack(side="left", padx=10)
self.lbl_total.pack(side="right", padx=10)
actions = ctk.CTkFrame(self.tab_invoice, corner_radius=16)
actions.pack(fill="x", padx=10, pady=(0,10))
ctk.CTkButton(actions, text="Remove Selected Item", fg_color="#c62828", hover_color="#b71c1c", command=self.remove_selected_item).pack(side="left", padx=6)
ctk.CTkButton(actions, text="Clear Invoice", fg_color="#404040", command=self.clear_invoice).pack(side="left", padx=6)
self.invoice_items = [] # in-memory list until saved
def refresh_invoice_products(self):
conn = get_conn()
cur = conn.cursor()
cur.execute("SELECT id, name, sku, price, qty FROM products ORDER BY name ASC;")
self.products = cur.fetchall()
conn.close()
display = [f"{p[1]} ({p[2]}) – ₹{p[3]:.2f} [Stock {p[4]}]" for p in self.products]
self.cb_product["values"] = display
def add_invoice_item(self):
idx = self.cb_product.current()
if idx < 0:
messagebox.showinfo("Pick", "Choose a product")
return
qty = validate_int(self.ent_inv_qty.get(), 1)
pid, name, sku, price, stock_qty = self.products[idx]
if qty <= 0:
messagebox.showwarning("Qty", "Quantity must be positive")
return
if qty > stock_qty:
messagebox.showwarning("Stock", f"Only {stock_qty} in stock for {sku}")
return
line_total = price * qty
self.invoice_items.append({"product_id": pid, "name": name, "sku": sku, "qty": qty, "unit_price": price, "line_total": line_total})
self.tv_inv.insert("", "end", values=(name, sku, qty, f"{price:.2f}", f"{line_total:.2f}"))
self.update_totals()
def remove_selected_item(self):
sel = self.tv_inv.selection()
if not sel:
return
idx = self.tv_inv.index(sel[0])
self.tv_inv.delete(sel[0])
del self.invoice_items[idx]
self.update_totals()
def clear_invoice(self):
for i in self.tv_inv.get_children():
self.tv_inv.delete(i)
self.invoice_items.clear()
self.update_totals()
def update_totals(self):
subtotal = sum(item["line_total"] for item in self.invoice_items)
tax_rate = validate_float(self.ent_tax.get(), 0.0)
tax_amt = subtotal * (tax_rate/100.0)
total = subtotal + tax_amt
self.lbl_subtotal.configure(text=f"Subtotal: {money(subtotal)}")
self.lbl_tax.configure(text=f"Tax ({tax_rate:.2f}%): {money(tax_amt)}")
self.lbl_total.configure(text=f"Total: {money(total)}")
def save_invoice(self):
customer = self.ent_customer.get().strip() or "Walk-in"
if not self.invoice_items:
messagebox.showinfo("Empty", "Add at least one item")
return
subtotal = sum(item["line_total"] for item in self.invoice_items)
tax_rate = validate_float(self.ent_tax.get(), 0.0)
tax_amt = subtotal * (tax_rate/100.0)
total = subtotal + tax_amt
created_at = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
conn = get_conn()
cur = conn.cursor()
cur.execute("""INSERT INTO invoices(customer, created_at, subtotal, tax_rate, tax_amount, total)
VALUES(?,?,?,?,?,?)""", (customer, created_at, subtotal, tax_rate, tax_amt, total))
inv_id = cur.lastrowid
# Decrease stock and insert items
for it in self.invoice_items:
cur.execute("""INSERT INTO invoice_items(invoice_id, product_id, qty, unit_price, line_total)
VALUES(?,?,?,?,?)""", (inv_id, it["product_id"], it["qty"], it["unit_price"], it["line_total"]))
cur.execute("UPDATE products SET qty = qty - ? WHERE id=?", (it["qty"], it["product_id"]))
conn.commit()
conn.close()
# Offer export CSV
if messagebox.askyesno("Saved", f"Invoice #{inv_id} saved.\nExport CSV now?"):
self.export_single_invoice_csv(inv_id)
self.clear_invoice()
self.refresh_products()
self.refresh_invoice_products()
# --------------- Exports ---------------
def export_stock_csv(self):
path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV","*.csv")], initialfile="stock_export.csv")
if not path: return
conn = get_conn()
cur = conn.cursor()
cur.execute("SELECT id,name,sku,price,qty,created_at FROM products ORDER BY id;")
rows = cur.fetchall()
conn.close()
with open(path, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["ID","Name","SKU","Price","Qty","Created At"])
writer.writerows(rows)
messagebox.showinfo("Exported", f"Stock exported to {os.path.basename(path)}")
def export_invoice_csv(self):
path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV","*.csv")], initialfile="invoices_export.csv")
if not path: return
conn = get_conn()
cur = conn.cursor()
cur.execute("""SELECT i.id, i.customer, i.created_at, i.subtotal, i.tax_rate, i.tax_amount, i.total
FROM invoices i ORDER BY i.id;""")
invoices = cur.fetchall()
with open(path, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["InvoiceID","Customer","CreatedAt","Subtotal","TaxRate%","TaxAmount","Total"])
writer.writerows(invoices)
conn.close()
messagebox.showinfo("Exported", f"Invoices exported to {os.path.basename(path)}")
def export_single_invoice_csv(self, inv_id: int):
path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV","*.csv")], initialfile=f"invoice_{inv_id}.csv")
if not path: return
conn = get_conn()
cur = conn.cursor()
cur.execute("""SELECT i.id, i.customer, i.created_at, i.subtotal, i.tax_rate, i.tax_amount, i.total
FROM invoices i WHERE i.id=?""", (inv_id,))
inv = cur.fetchone()
cur.execute("""SELECT p.name, p.sku, ii.qty, ii.unit_price, ii.line_total
FROM invoice_items ii
JOIN products p ON p.id = ii.product_id
WHERE ii.invoice_id=?""", (inv_id,))
items = cur.fetchall()
conn.close()
with open(path, "w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow(["Invoice", inv[0]])
w.writerow(["Customer", inv[1]])
w.writerow(["Date", inv[2]])
w.writerow([])
w.writerow(["Name","SKU","Qty","Unit Price","Line Total"])
w.writerows(items)
w.writerow([])
w.writerow(["Subtotal", f"{inv[3]:.2f}"])
w.writerow(["Tax %", f"{inv[4]:.2f}"])
w.writerow(["Tax Amount", f"{inv[5]:.2f}"])
w.writerow(["Total", f"{inv[6]:.2f}"])
messagebox.showinfo("Exported", f"Invoice saved to {os.path.basename(path)}")
def main():
init_db()
app = App()
app.mainloop()
if __name__ == "__main__":
main()
Comments
Post a Comment