πŸš€ 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

Popular posts from this blog

πŸš€ Simple Login & Registration System in Python Tkinter πŸ“±

πŸš€ Create a Python Screen Recorder with Audio (Complete Code)

πŸ“‘ Fuzzu Packet Sniffer – Python GUI for Real-Time IP Monitoring | Tkinter + Scapy