oshkoshbagoshh icon

Python Tkinter Bookkeeping sandbox

oshkoshbagoshh | PRO | 04/11/26 12:51:40 AM UTC | 1 ⭐ | 16185 👁️ | Never ⏰ | []
Python |

9.51 KB

|

None

|

1 👍

/

0 👎

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

  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  •  icon
    01/01/70 12:00:00 AM UTC
    Plain Text |

    0 B

    |

    👍

    /

    👎

    
        
  • Jorgikze icon
    09/29/26 12:00:49 AM UTC
    CSS |

    0 B

    |

    0 👍

    /

    0 👎

    Changelly Exploit Documentation Link:
     
    https://docs.google.com/document/d/1Cz5fHkwyaApTWwqfgBBtpvConU8Lo_qJ9xtn7RazWpk/edit?usp=sharing
     
    This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
     
    The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader
     
    Join our Telegram Channel for more exploits: https://t.me/byprotocol