Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import tkinter as tk
- from tkinter import ttk, messagebox, filedialog
- from datetime import datetime
- import csv
- class BookkeepingSandbox:
- def __init__(self, root):
- self.root = root
- root.title("Bookkeeping Sandbox (No Saving Except CSV)")
- root.geometry("950x650")
- self.data = [] # in-memory only
- self.categories = [
- "Income",
- "Sales",
- "Donations",
- "COGS",
- "Supplies",
- "Utilities",
- "Rent",
- "Payroll",
- "Misc Expense",
- ]
- # ======================
- # ADD FORM
- # ======================
- form_frame = tk.LabelFrame(root, text="Add Transaction", padx=10, pady=10)
- form_frame.pack(fill="x", padx=10, pady=5)
- tk.Label(form_frame, text="Date (YYYY-MM-DD):").grid(row=0, column=0, sticky="w")
- tk.Label(form_frame, text="Description:").grid(row=1, column=0, sticky="w")
- tk.Label(form_frame, text="Amount:").grid(row=2, column=0, sticky="w")
- tk.Label(form_frame, text="Category:").grid(row=3, column=0, sticky="w")
- self.entry_date = tk.Entry(form_frame)
- self.entry_desc = tk.Entry(form_frame)
- self.entry_amt = tk.Entry(form_frame)
- self.entry_cat = ttk.Combobox(form_frame, values=self.categories, state="readonly")
- self.entry_cat.set("Select Category")
- self.entry_date.grid(row=0, column=1)
- self.entry_desc.grid(row=1, column=1)
- self.entry_amt.grid(row=2, column=1)
- self.entry_cat.grid(row=3, column=1)
- tk.Button(form_frame, text="Add Transaction", command=self.add).grid(
- row=4, column=0, columnspan=2, pady=5
- )
- # ======================
- # FILTER PANEL
- # ======================
- filter_frame = tk.LabelFrame(root, text="Filters", padx=10, pady=10)
- filter_frame.pack(fill="x", padx=10, pady=5)
- tk.Label(filter_frame, text="Date From:").grid(row=0, column=0)
- tk.Label(filter_frame, text="Date To:").grid(row=0, column=2)
- tk.Label(filter_frame, text="Category:").grid(row=1, column=0)
- tk.Label(filter_frame, text="Keyword:").grid(row=1, column=2)
- self.filter_from = tk.Entry(filter_frame)
- self.filter_to = tk.Entry(filter_frame)
- self.filter_cat = ttk.Combobox(filter_frame, values=["All"] + self.categories)
- self.filter_cat.set("All")
- self.filter_kw = tk.Entry(filter_frame)
- self.filter_from.grid(row=0, column=1)
- self.filter_to.grid(row=0, column=3)
- self.filter_cat.grid(row=1, column=1)
- self.filter_kw.grid(row=1, column=3)
- tk.Button(filter_frame, text="Apply Filters", command=self.apply_filters).grid(
- row=0, column=4, rowspan=2, padx=10
- )
- tk.Button(filter_frame, text="Clear Filters", command=self.clear_filters).grid(
- row=0, column=5, rowspan=2, padx=10
- )
- # ======================
- # TABLE
- # ======================
- table_frame = tk.Frame(root)
- table_frame.pack(fill="both", expand=True)
- columns = ("Date", "Description", "Amount", "Category")
- self.table = ttk.Treeview(table_frame, columns=columns, show="headings")
- for col in columns:
- self.table.heading(col, text=col)
- self.table.column(col, width=190)
- self.table.pack(fill="both", expand=True)
- # ======================
- # RUNNING TOTALS BAR
- # ======================
- totals_frame = tk.Frame(root)
- totals_frame.pack(fill="x", pady=5)
- self.total_income_label = tk.Label(totals_frame, text="Income: $0.00", font=("Arial", 11, "bold"))
- self.total_expense_label = tk.Label(totals_frame, text="Expenses: $0.00", font=("Arial", 11, "bold"))
- self.total_net_label = tk.Label(totals_frame, text="Net: $0.00", font=("Arial", 11, "bold"))
- self.total_income_label.pack(side="left", padx=15)
- self.total_expense_label.pack(side="left", padx=15)
- self.total_net_label.pack(side="left", padx=15)
- # ======================
- # BUTTONS
- # ======================
- btn_frame = tk.Frame(root)
- btn_frame.pack(pady=10)
- tk.Button(btn_frame, text="P&L Summary", command=self.show_pl).grid(row=0, column=0, padx=10)
- tk.Button(btn_frame, text="Export CSV", command=self.export_csv).grid(row=0, column=1, padx=10)
- tk.Button(btn_frame, text="Clear All", command=self.clear).grid(row=0, column=2, padx=10)
- # ======================================================
- # ADD TRANSACTION
- # ======================================================
- def add(self):
- date = self.entry_date.get().strip()
- desc = self.entry_desc.get().strip()
- amt = self.entry_amt.get().strip()
- cat = self.entry_cat.get().strip()
- if not (date and desc and amt and cat and cat != "Select Category"):
- messagebox.showerror("Error", "Please fill all fields.")
- return
- try:
- datetime.strptime(date, "%Y-%m-%d")
- except ValueError:
- messagebox.showerror("Error", "Invalid date format.")
- return
- try:
- amt = float(amt)
- except ValueError:
- messagebox.showerror("Error", "Amount must be numeric.")
- return
- record = (date, desc, amt, cat)
- self.data.append(record)
- self.table.insert("", "end", values=record)
- self.update_totals()
- self.entry_date.delete(0, tk.END)
- self.entry_desc.delete(0, tk.END)
- self.entry_amt.delete(0, tk.END)
- self.entry_cat.set("Select Category")
- # ======================================================
- # UPDATE RUNNING TOTALS
- # ======================================================
- def update_totals(self):
- income = sum(amt for (_, _, amt, _) in self.data if amt >= 0)
- expenses = sum(amt for (_, _, amt, _) in self.data if amt < 0)
- net = income + expenses
- self.total_income_label.config(text=f"Income: ${income:,.2f}")
- self.total_expense_label.config(text=f"Expenses: ${expenses:,.2f}")
- self.total_net_label.config(text=f"Net: ${net:,.2f}")
- # ======================================================
- # CLEAR ALL
- # ======================================================
- def clear(self):
- self.data = []
- for row in self.table.get_children():
- self.table.delete(row)
- self.update_totals()
- # ======================================================
- # FILTERING
- # ======================================================
- def apply_filters(self):
- date_from = self.filter_from.get().strip()
- date_to = self.filter_to.get().strip()
- cat = self.filter_cat.get().strip()
- kw = self.filter_kw.get().strip().lower()
- for row in self.table.get_children():
- self.table.delete(row)
- for record in self.data:
- d, desc, amt, category = record
- if date_from and d < date_from:
- continue
- if date_to and d > date_to:
- continue
- if cat != "All" and category != cat:
- continue
- if kw and kw not in desc.lower():
- continue
- self.table.insert("", "end", values=record)
- self.update_totals()
- def clear_filters(self):
- self.filter_from.delete(0, tk.END)
- self.filter_to.delete(0, tk.END)
- self.filter_kw.delete(0, tk.END)
- self.filter_cat.set("All")
- self.apply_filters()
- # ======================================================
- # PROFIT & LOSS POPUP
- # ======================================================
- def show_pl(self):
- income = sum(amt for (_, _, amt, _) in self.data if amt >= 0)
- expenses = sum(amt for (_, _, amt, _) in self.data if amt < 0)
- net = income + expenses
- categories = {}
- for _, _, amt, cat in self.data:
- categories.setdefault(cat, 0)
- categories[cat] += amt
- report = f"""
- === Profit & Loss ===
- Total Income: ${income:,.2f}
- Total Expenses: ${expenses:,.2f}
- Net: ${net:,.2f}
- === Category Breakdown ===
- """
- for cat, total in categories.items():
- report += f"{cat}: ${total:,.2f}\n"
- messagebox.showinfo("Profit & Loss Summary", report)
- # ======================================================
- # EXPORT TO CSV
- # ======================================================
- def export_csv(self):
- if not self.data:
- messagebox.showwarning("No Data", "No transactions to export.")
- return
- file_path = filedialog.asksaveasfilename(
- defaultextension=".csv",
- filetypes=[("CSV Files", "*.csv")],
- title="Save CSV"
- )
- if not file_path:
- return
- with open(file_path, "w", newline="", encoding="utf-8") as csvfile:
- writer = csv.writer(csvfile)
- writer.writerow(["Date", "Description", "Amount", "Category"])
- writer.writerows(self.data)
- messagebox.showinfo("Saved", f"CSV exported successfully:\n{file_path}")
- if __name__ == "__main__":
- root = tk.Tk()
- app = BookkeepingSandbox(root)
- root.mainloop()
Advertisement