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()
Comments
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|0 👍
/0 👎