Offline Travel Expense Splitter App

import tkinter as tk

from tkinter import ttk, messagebox

import sqlite3

from fpdf import FPDF


# ---------------------------------------------------

# DATABASE SETUP

# ---------------------------------------------------

conn = sqlite3.connect("travel_expenses.db")

cursor = conn.cursor()


cursor.execute("""

CREATE TABLE IF NOT EXISTS members (

    id INTEGER PRIMARY KEY AUTOINCREMENT,

    name TEXT NOT NULL

)

""")


cursor.execute("""

CREATE TABLE IF NOT EXISTS expenses (

    id INTEGER PRIMARY KEY AUTOINCREMENT,

    member_id INTEGER,

    description TEXT,

    amount REAL,

    FOREIGN KEY(member_id) REFERENCES members(id)

)

""")


conn.commit()


# ---------------------------------------------------

# MAIN APPLICATION

# ---------------------------------------------------

class TravelExpenseApp:


    def __init__(self, root):

        self.root = root

        self.root.title("Travel Expense Splitter")

        self.root.geometry("600x500")


        self.create_ui()


    # ------------------------ UI ------------------------

    def create_ui(self):

        tab_control = ttk.Notebook(self.root)


        self.tab_members = ttk.Frame(tab_control)

        self.tab_expenses = ttk.Frame(tab_control)

        self.tab_summary = ttk.Frame(tab_control)


        tab_control.add(self.tab_members, text="Members")

        tab_control.add(self.tab_expenses, text="Expenses")

        tab_control.add(self.tab_summary, text="Summary")

        tab_control.pack(expand=1, fill="both")


        self.setup_members_tab()

        self.setup_expenses_tab()

        self.setup_summary_tab()


    # ------------------------ MEMBERS TAB ------------------------

    def setup_members_tab(self):

        tk.Label(self.tab_members, text="Add Member", font=("Arial", 14)).pack(pady=10)


        self.member_entry = tk.Entry(self.tab_members, font=("Arial", 12))

        self.member_entry.pack(pady=5)


        tk.Button(self.tab_members, text="Add Member", command=self.add_member).pack(pady=10)


        self.members_list = tk.Listbox(self.tab_members, width=40, height=10)

        self.members_list.pack(pady=10)


        self.load_members()


    def add_member(self):

        name = self.member_entry.get()

        if not name:

            messagebox.showerror("Error", "Please enter a name!")

            return


        cursor.execute("INSERT INTO members (name) VALUES (?)", (name,))

        conn.commit()

        self.member_entry.delete(0, tk.END)

        self.load_members()


    def load_members(self):

        self.members_list.delete(0, tk.END)

        cursor.execute("SELECT name FROM members")

        for row in cursor.fetchall():

            self.members_list.insert(tk.END, row[0])


    # ------------------------ EXPENSE TAB ------------------------

    def setup_expenses_tab(self):

        tk.Label(self.tab_expenses, text="Add Expense", font=("Arial", 14)).pack(pady=10)


        tk.Label(self.tab_expenses, text="Select Member:").pack()

        self.member_dropdown = ttk.Combobox(self.tab_expenses, state="readonly")

        self.member_dropdown.pack(pady=5)


        self.load_member_dropdown()


        tk.Label(self.tab_expenses, text="Description:").pack()

        self.desc_entry = tk.Entry(self.tab_expenses, font=("Arial", 12))

        self.desc_entry.pack(pady=5)


        tk.Label(self.tab_expenses, text="Amount:").pack()

        self.amount_entry = tk.Entry(self.tab_expenses, font=("Arial", 12))

        self.amount_entry.pack(pady=5)


        tk.Button(self.tab_expenses, text="Add Expense", command=self.add_expense).pack(pady=10)


        self.expense_tree = ttk.Treeview(self.tab_expenses, columns=("member", "desc", "amount"), show="headings")

        self.expense_tree.heading("member", text="Member")

        self.expense_tree.heading("desc", text="Description")

        self.expense_tree.heading("amount", text="Amount")

        self.expense_tree.pack(fill="both", expand=True)


        self.load_expenses()


    def load_member_dropdown(self):

        cursor.execute("SELECT name FROM members")

        members = [row[0] for row in cursor.fetchall()]

        self.member_dropdown["values"] = members


    def add_expense(self):

        member = self.member_dropdown.get()

        desc = self.desc_entry.get()

        amount = self.amount_entry.get()


        if not member or not desc or not amount:

            messagebox.showerror("Error", "All fields are required!")

            return


        try:

            amount = float(amount)

        except:

            messagebox.showerror("Error", "Amount must be a number!")

            return


        cursor.execute("SELECT id FROM members WHERE name=?", (member,))

        member_id = cursor.fetchone()[0]


        cursor.execute("INSERT INTO expenses (member_id, description, amount) VALUES (?, ?, ?)",

                       (member_id, desc, amount))

        conn.commit()


        self.desc_entry.delete(0, tk.END)

        self.amount_entry.delete(0, tk.END)


        self.load_expenses()


    def load_expenses(self):

        for i in self.expense_tree.get_children():

            self.expense_tree.delete(i)


        cursor.execute("""

            SELECT members.name, expenses.description, expenses.amount

            FROM expenses

            JOIN members ON expenses.member_id = members.id

        """)


        for row in cursor.fetchall():

            self.expense_tree.insert("", tk.END, values=row)


    # ------------------------ SUMMARY TAB ------------------------

    def setup_summary_tab(self):

        tk.Label(self.tab_summary, text="Expense Summary", font=("Arial", 14)).pack(pady=10)


        tk.Button(self.tab_summary, text="Calculate Summary", command=self.calculate_summary).pack(pady=10)

        tk.Button(self.tab_summary, text="Export PDF", command=self.export_pdf).pack(pady=10)


        self.summary_box = tk.Text(self.tab_summary, height=20, width=70)

        self.summary_box.pack()


    def calculate_summary(self):

        cursor.execute("SELECT COUNT(*) FROM members")

        num_members = cursor.fetchone()[0]


        cursor.execute("SELECT SUM(amount) FROM expenses")

        total_expense = cursor.fetchone()[0] or 0


        split_amount = total_expense / num_members if num_members > 0 else 0


        self.summary_box.delete(1.0, tk.END)

        self.summary_box.insert(tk.END, f"Total Expense: ₹{total_expense:.2f}\n")

        self.summary_box.insert(tk.END, f"Each Person Should Pay: ₹{split_amount:.2f}\n\n")


        cursor.execute("""

            SELECT members.name, SUM(expenses.amount)

            FROM members

            LEFT JOIN expenses ON members.id = expenses.member_id

            GROUP BY members.name

        """)


        for name, paid in cursor.fetchall():

            paid = paid or 0

            diff = paid - split_amount

            status = "owes" if diff < 0 else "gets back"

            self.summary_box.insert(tk.END, f"{name}: Paid ₹{paid:.2f} → {status} ₹{abs(diff):.2f}\n")


    # ------------------------ PDF EXPORT ------------------------

    def export_pdf(self):

        pdf = FPDF()

        pdf.add_page()

        pdf.set_font("Arial", size=12)


        pdf.cell(200, 10, "Travel Expense Summary", ln=True, align="C")

        pdf.ln(10)


        summary_text = self.summary_box.get(1.0, tk.END).split("\n")

        for line in summary_text:

            pdf.cell(0, 10, txt=line, ln=True)


        pdf.output("Travel_Expense_Summary.pdf")

        messagebox.showinfo("Success", "PDF Exported Successfully!")


# ------------------------ RUN APP ------------------------

root = tk.Tk()

app = TravelExpenseApp(root)

root.mainloop()


No comments: