oshkoshbagoshh

Python Tkinter Bookkeeping sandbox

Apr 10th, 2026
10,030
1
Never
56
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.51 KB | None | 1 0
  1. import tkinter as tk
  2. from tkinter import ttk, messagebox, filedialog
  3. from datetime import datetime
  4. import csv
  5.  
  6.  
  7. class BookkeepingSandbox:
  8.     def __init__(self, root):
  9.         self.root = root
  10.         root.title("Bookkeeping Sandbox (No Saving Except CSV)")
  11.         root.geometry("950x650")
  12.  
  13.         self.data = []  # in-memory only
  14.  
  15.         self.categories = [
  16.             "Income",
  17.             "Sales",
  18.             "Donations",
  19.             "COGS",
  20.             "Supplies",
  21.             "Utilities",
  22.             "Rent",
  23.             "Payroll",
  24.             "Misc Expense",
  25.         ]
  26.  
  27.         # ======================
  28.         # ADD FORM
  29.         # ======================
  30.         form_frame = tk.LabelFrame(root, text="Add Transaction", padx=10, pady=10)
  31.         form_frame.pack(fill="x", padx=10, pady=5)
  32.  
  33.         tk.Label(form_frame, text="Date (YYYY-MM-DD):").grid(row=0, column=0, sticky="w")
  34.         tk.Label(form_frame, text="Description:").grid(row=1, column=0, sticky="w")
  35.         tk.Label(form_frame, text="Amount:").grid(row=2, column=0, sticky="w")
  36.         tk.Label(form_frame, text="Category:").grid(row=3, column=0, sticky="w")
  37.  
  38.         self.entry_date = tk.Entry(form_frame)
  39.         self.entry_desc = tk.Entry(form_frame)
  40.         self.entry_amt = tk.Entry(form_frame)
  41.  
  42.         self.entry_cat = ttk.Combobox(form_frame, values=self.categories, state="readonly")
  43.         self.entry_cat.set("Select Category")
  44.  
  45.         self.entry_date.grid(row=0, column=1)
  46.         self.entry_desc.grid(row=1, column=1)
  47.         self.entry_amt.grid(row=2, column=1)
  48.         self.entry_cat.grid(row=3, column=1)
  49.  
  50.         tk.Button(form_frame, text="Add Transaction", command=self.add).grid(
  51.             row=4, column=0, columnspan=2, pady=5
  52.         )
  53.  
  54.         # ======================
  55.         # FILTER PANEL
  56.         # ======================
  57.         filter_frame = tk.LabelFrame(root, text="Filters", padx=10, pady=10)
  58.         filter_frame.pack(fill="x", padx=10, pady=5)
  59.  
  60.         tk.Label(filter_frame, text="Date From:").grid(row=0, column=0)
  61.         tk.Label(filter_frame, text="Date To:").grid(row=0, column=2)
  62.         tk.Label(filter_frame, text="Category:").grid(row=1, column=0)
  63.         tk.Label(filter_frame, text="Keyword:").grid(row=1, column=2)
  64.  
  65.         self.filter_from = tk.Entry(filter_frame)
  66.         self.filter_to = tk.Entry(filter_frame)
  67.  
  68.         self.filter_cat = ttk.Combobox(filter_frame, values=["All"] + self.categories)
  69.         self.filter_cat.set("All")
  70.  
  71.         self.filter_kw = tk.Entry(filter_frame)
  72.  
  73.         self.filter_from.grid(row=0, column=1)
  74.         self.filter_to.grid(row=0, column=3)
  75.         self.filter_cat.grid(row=1, column=1)
  76.         self.filter_kw.grid(row=1, column=3)
  77.  
  78.         tk.Button(filter_frame, text="Apply Filters", command=self.apply_filters).grid(
  79.             row=0, column=4, rowspan=2, padx=10
  80.         )
  81.         tk.Button(filter_frame, text="Clear Filters", command=self.clear_filters).grid(
  82.             row=0, column=5, rowspan=2, padx=10
  83.         )
  84.  
  85.         # ======================
  86.         # TABLE
  87.         # ======================
  88.         table_frame = tk.Frame(root)
  89.         table_frame.pack(fill="both", expand=True)
  90.  
  91.         columns = ("Date", "Description", "Amount", "Category")
  92.         self.table = ttk.Treeview(table_frame, columns=columns, show="headings")
  93.  
  94.         for col in columns:
  95.             self.table.heading(col, text=col)
  96.             self.table.column(col, width=190)
  97.  
  98.         self.table.pack(fill="both", expand=True)
  99.  
  100.         # ======================
  101.         # RUNNING TOTALS BAR
  102.         # ======================
  103.         totals_frame = tk.Frame(root)
  104.         totals_frame.pack(fill="x", pady=5)
  105.  
  106.         self.total_income_label = tk.Label(totals_frame, text="Income: $0.00", font=("Arial", 11, "bold"))
  107.         self.total_expense_label = tk.Label(totals_frame, text="Expenses: $0.00", font=("Arial", 11, "bold"))
  108.         self.total_net_label = tk.Label(totals_frame, text="Net: $0.00", font=("Arial", 11, "bold"))
  109.  
  110.         self.total_income_label.pack(side="left", padx=15)
  111.         self.total_expense_label.pack(side="left", padx=15)
  112.         self.total_net_label.pack(side="left", padx=15)
  113.  
  114.         # ======================
  115.         # BUTTONS
  116.         # ======================
  117.         btn_frame = tk.Frame(root)
  118.         btn_frame.pack(pady=10)
  119.  
  120.         tk.Button(btn_frame, text="P&L Summary", command=self.show_pl).grid(row=0, column=0, padx=10)
  121.         tk.Button(btn_frame, text="Export CSV", command=self.export_csv).grid(row=0, column=1, padx=10)
  122.         tk.Button(btn_frame, text="Clear All", command=self.clear).grid(row=0, column=2, padx=10)
  123.  
  124.     # ======================================================
  125.     # ADD TRANSACTION
  126.     # ======================================================
  127.     def add(self):
  128.         date = self.entry_date.get().strip()
  129.         desc = self.entry_desc.get().strip()
  130.         amt = self.entry_amt.get().strip()
  131.         cat = self.entry_cat.get().strip()
  132.  
  133.         if not (date and desc and amt and cat and cat != "Select Category"):
  134.             messagebox.showerror("Error", "Please fill all fields.")
  135.             return
  136.  
  137.         try:
  138.             datetime.strptime(date, "%Y-%m-%d")
  139.         except ValueError:
  140.             messagebox.showerror("Error", "Invalid date format.")
  141.             return
  142.  
  143.         try:
  144.             amt = float(amt)
  145.         except ValueError:
  146.             messagebox.showerror("Error", "Amount must be numeric.")
  147.             return
  148.  
  149.         record = (date, desc, amt, cat)
  150.         self.data.append(record)
  151.         self.table.insert("", "end", values=record)
  152.  
  153.         self.update_totals()
  154.  
  155.         self.entry_date.delete(0, tk.END)
  156.         self.entry_desc.delete(0, tk.END)
  157.         self.entry_amt.delete(0, tk.END)
  158.         self.entry_cat.set("Select Category")
  159.  
  160.     # ======================================================
  161.     # UPDATE RUNNING TOTALS
  162.     # ======================================================
  163.     def update_totals(self):
  164.         income = sum(amt for (_, _, amt, _) in self.data if amt >= 0)
  165.         expenses = sum(amt for (_, _, amt, _) in self.data if amt < 0)
  166.         net = income + expenses
  167.  
  168.         self.total_income_label.config(text=f"Income: ${income:,.2f}")
  169.         self.total_expense_label.config(text=f"Expenses: ${expenses:,.2f}")
  170.         self.total_net_label.config(text=f"Net: ${net:,.2f}")
  171.  
  172.     # ======================================================
  173.     # CLEAR ALL
  174.     # ======================================================
  175.     def clear(self):
  176.         self.data = []
  177.         for row in self.table.get_children():
  178.             self.table.delete(row)
  179.         self.update_totals()
  180.  
  181.     # ======================================================
  182.     # FILTERING
  183.     # ======================================================
  184.     def apply_filters(self):
  185.         date_from = self.filter_from.get().strip()
  186.         date_to = self.filter_to.get().strip()
  187.         cat = self.filter_cat.get().strip()
  188.         kw = self.filter_kw.get().strip().lower()
  189.  
  190.         for row in self.table.get_children():
  191.             self.table.delete(row)
  192.  
  193.         for record in self.data:
  194.             d, desc, amt, category = record
  195.  
  196.             if date_from and d < date_from:
  197.                 continue
  198.             if date_to and d > date_to:
  199.                 continue
  200.             if cat != "All" and category != cat:
  201.                 continue
  202.             if kw and kw not in desc.lower():
  203.                 continue
  204.  
  205.             self.table.insert("", "end", values=record)
  206.  
  207.         self.update_totals()
  208.  
  209.     def clear_filters(self):
  210.         self.filter_from.delete(0, tk.END)
  211.         self.filter_to.delete(0, tk.END)
  212.         self.filter_kw.delete(0, tk.END)
  213.         self.filter_cat.set("All")
  214.         self.apply_filters()
  215.  
  216.     # ======================================================
  217.     # PROFIT & LOSS POPUP
  218.     # ======================================================
  219.     def show_pl(self):
  220.         income = sum(amt for (_, _, amt, _) in self.data if amt >= 0)
  221.         expenses = sum(amt for (_, _, amt, _) in self.data if amt < 0)
  222.         net = income + expenses
  223.  
  224.         categories = {}
  225.         for _, _, amt, cat in self.data:
  226.             categories.setdefault(cat, 0)
  227.             categories[cat] += amt
  228.  
  229.         report = f"""
  230. === Profit & Loss ===
  231.  
  232. Total Income: ${income:,.2f}
  233. Total Expenses: ${expenses:,.2f}
  234. Net: ${net:,.2f}
  235.  
  236. === Category Breakdown ===
  237. """
  238.         for cat, total in categories.items():
  239.             report += f"{cat}: ${total:,.2f}\n"
  240.  
  241.         messagebox.showinfo("Profit & Loss Summary", report)
  242.  
  243.     # ======================================================
  244.     # EXPORT TO CSV
  245.     # ======================================================
  246.     def export_csv(self):
  247.         if not self.data:
  248.             messagebox.showwarning("No Data", "No transactions to export.")
  249.             return
  250.  
  251.         file_path = filedialog.asksaveasfilename(
  252.             defaultextension=".csv",
  253.             filetypes=[("CSV Files", "*.csv")],
  254.             title="Save CSV"
  255.         )
  256.  
  257.         if not file_path:
  258.             return
  259.  
  260.         with open(file_path, "w", newline="", encoding="utf-8") as csvfile:
  261.             writer = csv.writer(csvfile)
  262.             writer.writerow(["Date", "Description", "Amount", "Category"])
  263.             writer.writerows(self.data)
  264.  
  265.         messagebox.showinfo("Saved", f"CSV exported successfully:\n{file_path}")
  266.  
  267.  
  268. if __name__ == "__main__":
  269.     root = tk.Tk()
  270.     app = BookkeepingSandbox(root)
  271.     root.mainloop()
  272.  
  273.  
Advertisement