Create a script that creates the following project setting up the folder structure and generating the code for every aspect setting subgoals and fully coding every part to form a comprehensive accounting system similar to QuickBooks Pro using Python and that PyQt6 involves several components, including a robust GUI, database management, invoicing capabilities, and integration with external email services for sending invoices. Given the complexity and the scope of the project, it's essential to structure the code efficiently, utilize object-oriented programming principles, and ensure scalability.

Below is some code to start with, incorporating a PyQt6-based GUI, enhanced functionalities for managing invoices, and the ability to send invoices via external email services like Gmail.

Project Structure
To manage the complexity, it's advisable to structure the project into multiple modules. Here's a suggested structure:


accounting_system/
├── main.py
├── database.py
├── models.py
├── views/
│   ├── main_window.py
│   ├── add_account_dialog.py
│   ├── add_transaction_dialog.py
│   ├── invoice_dialog.py
│   └── ...
├── controllers/
│   ├── account_controller.py
│   ├── transaction_controller.py
│   ├── invoice_controller.py
│   └── ...
├── utils/
│   ├── email_sender.py
│   └── ...
├── resources/
│   └── icons/
│       └── ...
└── requirements.txt
import sys
import sqlite3
from datetime import datetime
import smtplib
import ssl
from email.message import EmailMessage
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QDialog, QMessageBox, QPushButton,
    QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTextEdit, QComboBox,
    QTableWidget, QTableWidgetItem, QDateEdit, QSpinBox, QDoubleSpinBox
)
from PyQt6.QtCore import Qt, QDate

# ---------------------------
# Database Management
# ---------------------------

DB_NAME = 'accounting_system.db'

class Database:
    def __init__(self, db_name=DB_NAME):
        self.conn = sqlite3.connect(db_name)
        self.cursor = self.conn.cursor()
        self.initialize_db()

    def initialize_db(self):
        # Enable foreign key constraints
        self.cursor.execute("PRAGMA foreign_keys = ON;")

        # Create accounts table
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS accounts (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                name TEXT NOT NULL UNIQUE,
                type TEXT NOT NULL CHECK(type IN ('Assets', 'Liabilities', 'Equity', 'Income', 'Expenses'))
            )
        ''')

        # Create transactions table
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS transactions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                date TEXT NOT NULL,
                description TEXT,
                debit_account_id INTEGER NOT NULL,
                credit_account_id INTEGER NOT NULL,
                amount REAL NOT NULL CHECK(amount > 0),
                FOREIGN KEY(debit_account_id) REFERENCES accounts(id),
                FOREIGN KEY(credit_account_id) REFERENCES accounts(id)
            )
        ''')

        # Create invoices table
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS invoices (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                invoice_number TEXT NOT NULL UNIQUE,
                date TEXT NOT NULL,
                customer_name TEXT NOT NULL,
                customer_email TEXT NOT NULL,
                description TEXT,
                amount REAL NOT NULL CHECK(amount > 0),
                status TEXT NOT NULL CHECK(status IN ('Pending', 'Sent', 'Paid'))
            )
        ''')

        self.conn.commit()

    def execute(self, query, params=()):
        self.cursor.execute(query, params)
        self.conn.commit()
        return self.cursor

    def fetchall(self):
        return self.cursor.fetchall()

    def fetchone(self):
        return self.cursor.fetchone()

    def close(self):
        self.conn.close()

# ---------------------------
# Email Sender Utility
# ---------------------------

class EmailSender:
    def __init__(self, smtp_server, port, sender_email, sender_password):
        self.smtp_server = smtp_server
        self.port = port
        self.sender_email = sender_email
        self.sender_password = sender_password

    def send_email(self, receiver_email, subject, body, attachment=None):
        message = EmailMessage()
        message["From"] = self.sender_email
        message["To"] = receiver_email
        message["Subject"] = subject
        message.set_content(body)

        if attachment:
            with open(attachment, 'rb') as f:
                file_data = f.read()
                file_name = f.name
            message.add_attachment(file_data, maintype='application', subtype='octet-stream', filename=file_name)

        context = ssl.create_default_context()
        try:
            with smtplib.SMTP_SSL(self.smtp_server, self.port, context=context) as server:
                server.login(self.sender_email, self.sender_password)
                server.send_message(message)
            return True, "Email sent successfully."
        except Exception as e:
            return False, str(e)

# ---------------------------
# Add Account Dialog
# ---------------------------

class AddAccountDialog(QDialog):
    def __init__(self, db, parent=None):
        super().__init__(parent)
        self.db = db
        self.setWindowTitle("Add New Account")
        self.setFixedSize(300, 200)
        self.setup_ui()

    def setup_ui(self):
        layout = QVBoxLayout()

        # Account Name
        name_layout = QHBoxLayout()
        name_label = QLabel("Account Name:")
        self.name_input = QLineEdit()
        name_layout.addWidget(name_label)
        name_layout.addWidget(self.name_input)
        layout.addLayout(name_layout)

        # Account Type
        type_layout = QHBoxLayout()
        type_label = QLabel("Account Type:")
        self.type_combo = QComboBox()
        self.type_combo.addItems(['Assets', 'Liabilities', 'Equity', 'Income', 'Expenses'])
        type_layout.addWidget(type_label)
        type_layout.addWidget(self.type_combo)
        layout.addLayout(type_layout)

        # Buttons
        buttons_layout = QHBoxLayout()
        add_button = QPushButton("Add")
        add_button.clicked.connect(self.add_account)
        cancel_button = QPushButton("Cancel")
        cancel_button.clicked.connect(self.reject)
        buttons_layout.addWidget(add_button)
        buttons_layout.addWidget(cancel_button)
        layout.addLayout(buttons_layout)

        self.setLayout(layout)

    def add_account(self):
        name = self.name_input.text().strip()
        acc_type = self.type_combo.currentText()

        if not name:
            QMessageBox.warning(self, "Input Error", "Account name cannot be empty.")
            return

        try:
            self.db.execute(
                'INSERT INTO accounts (name, type) VALUES (?, ?)',
                (name, acc_type)
            )
            QMessageBox.information(self, "Success", f"Account '{name}' added successfully.")
            self.accept()
        except sqlite3.IntegrityError:
            QMessageBox.warning(self, "Error", f"Account '{name}' already exists.")

# ---------------------------
# Add Transaction Dialog
# ---------------------------

class AddTransactionDialog(QDialog):
    def __init__(self, db, parent=None):
        super().__init__(parent)
        self.db = db
        self.setWindowTitle("Add New Transaction")
        self.setFixedSize(400, 300)
        self.setup_ui()

    def setup_ui(self):
        layout = QVBoxLayout()

        # Description
        desc_layout = QHBoxLayout()
        desc_label = QLabel("Description:")
        self.desc_input = QLineEdit()
        desc_layout.addWidget(desc_label)
        desc_layout.addWidget(self.desc_input)
        layout.addLayout(desc_layout)

        # Date
        date_layout = QHBoxLayout()
        date_label = QLabel("Date:")
        self.date_input = QDateEdit()
        self.date_input.setCalendarPopup(True)
        self.date_input.setDate(QDate.currentDate())
        date_layout.addWidget(date_label)
        date_layout.addWidget(self.date_input)
        layout.addLayout(date_layout)

        # Debit Account
        debit_layout = QHBoxLayout()
        debit_label = QLabel("Debit Account:")
        self.debit_combo = QComboBox()
        accounts = self.db.fetchall('SELECT name FROM accounts')
        self.debit_combo.addItems([acc[0] for acc in accounts])
        debit_layout.addWidget(debit_label)
        debit_layout.addWidget(self.debit_combo)
        layout.addLayout(debit_layout)

        # Credit Account
        credit_layout = QHBoxLayout()
        credit_label = QLabel("Credit Account:")
        self.credit_combo = QComboBox()
        self.credit_combo.addItems([acc[0] for acc in accounts])
        credit_layout.addWidget(credit_label)
        credit_layout.addWidget(self.credit_combo)
        layout.addLayout(credit_layout)

        # Amount
        amount_layout = QHBoxLayout()
        amount_label = QLabel("Amount (ZAR):")
        self.amount_input = QDoubleSpinBox()
        self.amount_input.setMaximum(1000000000)
        self.amount_input.setPrefix("ZAR ")
        self.amount_input.setDecimals(2)
        amount_layout.addWidget(amount_label)
        amount_layout.addWidget(self.amount_input)
        layout.addLayout(amount_layout)

        # Buttons
        buttons_layout = QHBoxLayout()
        add_button = QPushButton("Add")
        add_button.clicked.connect(self.add_transaction)
        cancel_button = QPushButton("Cancel")
        cancel_button.clicked.connect(self.reject)
        buttons_layout.addWidget(add_button)
        buttons_layout.addWidget(cancel_button)
        layout.addLayout(buttons_layout)

        self.setLayout(layout)

    def add_transaction(self):
        description = self.desc_input.text().strip()
        date = self.date_input.date().toString("yyyy-MM-dd")
        debit_account = self.debit_combo.currentText()
        credit_account = self.credit_combo.currentText()
        amount = self.amount_input.value()

        if debit_account == credit_account:
            QMessageBox.warning(self, "Input Error", "Debit and Credit accounts must be different.")
            return

        if amount <= 0:
            QMessageBox.warning(self, "Input Error", "Amount must be greater than zero.")
            return

        # Fetch account IDs
        debit_id = self.db.fetchone('SELECT id FROM accounts WHERE name = ?', (debit_account,))[0]
        credit_id = self.db.fetchone('SELECT id FROM accounts WHERE name = ?', (credit_account,))[0]

        try:
            self.db.execute(
                '''
                INSERT INTO transactions (date, description, debit_account_id, credit_account_id, amount)
                VALUES (?, ?, ?, ?, ?)
                ''',
                (date, description, debit_id, credit_id, amount)
            )
            QMessageBox.information(self, "Success", "Transaction recorded successfully.")
            self.accept()
        except sqlite3.Error as e:
            QMessageBox.warning(self, "Error", f"Failed to record transaction: {e}")

# ---------------------------
# Add Invoice Dialog
# ---------------------------

class AddInvoiceDialog(QDialog):
    def __init__(self, db, email_sender, parent=None):
        super().__init__(parent)
        self.db = db
        self.email_sender = email_sender
        self.setWindowTitle("Create New Invoice")
        self.setFixedSize(400, 400)
        self.setup_ui()

    def setup_ui(self):
        layout = QVBoxLayout()

        # Invoice Number
        inv_num_layout = QHBoxLayout()
        inv_num_label = QLabel("Invoice Number:")
        self.inv_num_input = QLineEdit()
        inv_num_layout.addWidget(inv_num_label)
        inv_num_layout.addWidget(self.inv_num_input)
        layout.addLayout(inv_num_layout)

        # Customer Name
        cust_name_layout = QHBoxLayout()
        cust_name_label = QLabel("Customer Name:")
        self.cust_name_input = QLineEdit()
        cust_name_layout.addWidget(cust_name_label)
        cust_name_layout.addWidget(self.cust_name_input)
        layout.addLayout(cust_name_layout)

        # Customer Email
        cust_email_layout = QHBoxLayout()
        cust_email_label = QLabel("Customer Email:")
        self.cust_email_input = QLineEdit()
        cust_email_layout.addWidget(cust_email_label)
        cust_email_layout.addWidget(self.cust_email_input)
        layout.addLayout(cust_email_layout)

        # Date
        date_layout = QHBoxLayout()
        date_label = QLabel("Date:")
        self.date_input = QDateEdit()
        self.date_input.setCalendarPopup(True)
        self.date_input.setDate(QDate.currentDate())
        date_layout.addWidget(date_label)
        date_layout.addWidget(self.date_input)
        layout.addLayout(date_layout)

        # Description
        desc_label = QLabel("Description:")
        self.desc_input = QTextEdit()
        layout.addWidget(desc_label)
        layout.addWidget(self.desc_input)

        # Amount
        amount_layout = QHBoxLayout()
        amount_label = QLabel("Amount (ZAR):")
        self.amount_input = QDoubleSpinBox()
        self.amount_input.setMaximum(1000000000)
        self.amount_input.setPrefix("ZAR ")
        self.amount_input.setDecimals(2)
        amount_layout.addWidget(amount_label)
        amount_layout.addWidget(self.amount_input)
        layout.addLayout(amount_layout)

        # Buttons
        buttons_layout = QHBoxLayout()
        create_button = QPushButton("Create Invoice")
        create_button.clicked.connect(self.create_invoice)
        cancel_button = QPushButton("Cancel")
        cancel_button.clicked.connect(self.reject)
        buttons_layout.addWidget(create_button)
        buttons_layout.addWidget(cancel_button)
        layout.addLayout(buttons_layout)

        self.setLayout(layout)

    def create_invoice(self):
        invoice_number = self.inv_num_input.text().strip()
        customer_name = self.cust_name_input.text().strip()
        customer_email = self.cust_email_input.text().strip()
        date = self.date_input.date().toString("yyyy-MM-dd")
        description = self.desc_input.toPlainText().strip()
        amount = self.amount_input.value()

        if not all([invoice_number, customer_name, customer_email, description]):
            QMessageBox.warning(self, "Input Error", "All fields except amount are required.")
            return

        if amount <= 0:
            QMessageBox.warning(self, "Input Error", "Amount must be greater than zero.")
            return

        try:
            self.db.execute(
                '''
                INSERT INTO invoices (invoice_number, date, customer_name, customer_email, description, amount, status)
                VALUES (?, ?, ?, ?, ?, ?, 'Pending')
                ''',
                (invoice_number, date, customer_name, customer_email, description, amount)
            )
            QMessageBox.information(self, "Success", "Invoice created successfully.")
            self.accept()
        except sqlite3.IntegrityError:
            QMessageBox.warning(self, "Error", f"Invoice Number '{invoice_number}' already exists.")
        except sqlite3.Error as e:
            QMessageBox.warning(self, "Error", f"Failed to create invoice: {e}")

# ---------------------------
# Send Invoice Dialog
# ---------------------------

class SendInvoiceDialog(QDialog):
    def __init__(self, db, email_sender, parent=None):
        super().__init__(parent)
        self.db = db
        self.email_sender = email_sender
        self.setWindowTitle("Send Invoice")
        self.setFixedSize(400, 300)
        self.setup_ui()

    def setup_ui(self):
        layout = QVBoxLayout()

        # Select Invoice
        inv_layout = QHBoxLayout()
        inv_label = QLabel("Select Invoice:")
        self.inv_combo = QComboBox()
        invoices = self.db.fetchall("SELECT invoice_number, customer_email, status FROM invoices WHERE status = 'Pending'")
        self.invoices = {f"{inv[0]} - {inv[1]}": inv[0] for inv in invoices}
        self.inv_combo.addItems(self.invoices.keys())
        inv_layout.addWidget(inv_label)
        inv_layout.addWidget(self.inv_combo)
        layout.addLayout(inv_layout)

        # Email Subject
        subject_layout = QHBoxLayout()
        subject_label = QLabel("Email Subject:")
        self.subject_input = QLineEdit("Your Invoice from Our Company")
        subject_layout.addWidget(subject_label)
        subject_layout.addWidget(self.subject_input)
        layout.addLayout(subject_layout)

        # Email Body
        body_label = QLabel("Email Body:")
        self.body_input = QTextEdit("Dear Customer,\n\nPlease find attached your invoice.\n\nThank you for your business!")
        layout.addWidget(body_label)
        layout.addWidget(self.body_input)

        # Buttons
        buttons_layout = QHBoxLayout()
        send_button = QPushButton("Send")
        send_button.clicked.connect(self.send_invoice)
        cancel_button = QPushButton("Cancel")
        cancel_button.clicked.connect(self.reject)
        buttons_layout.addWidget(send_button)
        buttons_layout.addWidget(cancel_button)
        layout.addLayout(buttons_layout)

        self.setLayout(layout)

    def send_invoice(self):
        invoice_display = self.inv_combo.currentText()
        invoice_number = self.invoices.get(invoice_display)
        subject = self.subject_input.text().strip()
        body = self.body_input.toPlainText().strip()

        if not invoice_number:
            QMessageBox.warning(self, "Error", "No invoice selected.")
            return

        # Fetch invoice details
        invoice = self.db.fetchone('SELECT customer_email, description, amount FROM invoices WHERE invoice_number = ?', (invoice_number,))
        if not invoice:
            QMessageBox.warning(self, "Error", "Selected invoice not found.")
            return

        receiver_email = invoice[0]
        description = invoice[1]
        amount = invoice[2]

        # Generate invoice as plain text or PDF (for simplicity, we'll use plain text)
        invoice_content = f"""
        Invoice Number: {invoice_number}
        Date: {datetime.now().strftime('%Y-%m-%d')}
        
        Customer Name: {self.db.fetchone('SELECT customer_name FROM invoices WHERE invoice_number = ?', (invoice_number,))[0]}
        Customer Email: {receiver_email}

        Description:
        {description}

        Amount: ZAR {amount:.2f}

        Thank you for your business!
        """

        # Optionally, save invoice to a file
        invoice_filename = f"Invoice_{invoice_number}.txt"
        with open(invoice_filename, 'w') as f:
            f.write(invoice_content)

        # Send email
        success, message = self.email_sender.send_email(
            receiver_email=receiver_email,
            subject=subject,
            body=body,
            attachment=invoice_filename
        )

        if success:
            QMessageBox.information(self, "Success", "Invoice sent successfully.")
            # Update invoice status to 'Sent'
            self.db.execute('UPDATE invoices SET status = "Sent" WHERE invoice_number = ?', (invoice_number,))
            self.accept()
        else:
            QMessageBox.warning(self, "Error", f"Failed to send email: {message}")

# ---------------------------
# Main Window
# ---------------------------

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.db = Database()
        self.email_sender = None  # Will be initialized after email setup
        self.setWindowTitle("Simple Accounting System")
        self.setGeometry(100, 100, 800, 600)
        self.setup_ui()

    def setup_ui(self):
        # Create Menu
        menubar = self.menuBar()

        # File Menu
        file_menu = menubar.addMenu("File")
        exit_action = QtGui.QAction("Exit", self)
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # Manage Menu
        manage_menu = menubar.addMenu("Manage")
        add_account_action = QtGui.QAction("Add Account", self)
        add_account_action.triggered.connect(self.open_add_account_dialog)
        manage_menu.addAction(add_account_action)

        add_transaction_action = QtGui.QAction("Add Transaction", self)
        add_transaction_action.triggered.connect(self.open_add_transaction_dialog)
        manage_menu.addAction(add_transaction_action)

        # Invoices Menu
        invoices_menu = menubar.addMenu("Invoices")
        create_invoice_action = QtGui.QAction("Create Invoice", self)
        create_invoice_action.triggered.connect(self.open_add_invoice_dialog)
        invoices_menu.addAction(create_invoice_action)

        send_invoice_action = QtGui.QAction("Send Invoice", self)
        send_invoice_action.triggered.connect(self.open_send_invoice_dialog)
        invoices_menu.addAction(send_invoice_action)

        # Reports Menu
        reports_menu = menubar.addMenu("Reports")
        trial_balance_action = QtGui.QAction("Trial Balance", self)
        trial_balance_action.triggered.connect(self.show_trial_balance)
        reports_menu.addAction(trial_balance_action)

        balance_sheet_action = QtGui.QAction("Balance Sheet", self)
        balance_sheet_action.triggered.connect(self.show_balance_sheet)
        reports_menu.addAction(balance_sheet_action)

        profit_loss_action = QtGui.QAction("Profit & Loss", self)
        profit_loss_action.triggered.connect(self.show_profit_and_loss)
        reports_menu.addAction(profit_loss_action)

        # Emails Setup
        emails_menu = menubar.addMenu("Emails")
        setup_email_action = QtGui.QAction("Setup Email", self)
        setup_email_action.triggered.connect(self.setup_email)
        emails_menu.addAction(setup_email_action)

        # Central Widget
        self.central_widget = QWidget()
        self.setCentralWidget(self.central_widget)
        self.layout = QVBoxLayout()

        # Placeholder for main content
        self.info_label = QLabel("Welcome to the Simple Accounting System!")
        self.info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.layout.addWidget(self.info_label)

        self.central_widget.setLayout(self.layout)

    # -----------------------
    # Menu Actions
    # -----------------------

    def open_add_account_dialog(self):
        dialog = AddAccountDialog(self.db, self)
        if dialog.exec():
            pass  # Account added

    def open_add_transaction_dialog(self):
        dialog = AddTransactionDialog(self.db, self)
        if dialog.exec():
            pass  # Transaction added

    def open_add_invoice_dialog(self):
        dialog = AddInvoiceDialog(self.db, self.email_sender, self)
        if dialog.exec():
            pass  # Invoice created

    def open_send_invoice_dialog(self):
        dialog = SendInvoiceDialog(self.db, self.email_sender, self)
        if dialog.exec():
            pass  # Invoice sent

    def setup_email(self):
        dialog = EmailSetupDialog(self)
        if dialog.exec():
            smtp_server, port, sender_email, sender_password = dialog.get_email_config()
            self.email_sender = EmailSender(smtp_server, port, sender_email, sender_password)
            QMessageBox.information(self, "Email Setup", "Email configuration saved successfully.")

    # -----------------------
    # Report Views
    # -----------------------

    def show_trial_balance(self):
        dialog = ReportDialog(self.db, report_type='Trial Balance', parent=self)
        dialog.exec()

    def show_balance_sheet(self):
        dialog = ReportDialog(self.db, report_type='Balance Sheet', parent=self)
        dialog.exec()

    def show_profit_and_loss(self):
        dialog = ReportDialog(self.db, report_type='Profit & Loss', parent=self)
        dialog.exec()

# ---------------------------
# Email Setup Dialog
# ---------------------------

class EmailSetupDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Email Configuration")
        self.setFixedSize(400, 400)
        self.setup_ui()

    def setup_ui(self):
        layout = QVBoxLayout()

        # SMTP Server
        smtp_layout = QHBoxLayout()
        smtp_label = QLabel("SMTP Server:")
        self.smtp_input = QLineEdit("smtp.gmail.com")
        smtp_layout.addWidget(smtp_label)
        smtp_layout.addWidget(self.smtp_input)
        layout.addLayout(smtp_layout)

        # Port
        port_layout = QHBoxLayout()
        port_label = QLabel("Port:")
        self.port_input = QSpinBox()
        self.port_input.setMaximum(100000)
        self.port_input.setValue(465)
        port_layout.addWidget(port_label)
        port_layout.addWidget(self.port_input)
        layout.addLayout(port_layout)

        # Sender Email
        sender_email_layout = QHBoxLayout()
        sender_email_label = QLabel("Sender Email:")
        self.sender_email_input = QLineEdit()
        sender_email_layout.addWidget(sender_email_label)
        sender_email_layout.addWidget(self.sender_email_input)
        layout.addLayout(sender_email_layout)

        # Sender Password
        sender_password_layout = QHBoxLayout()
        sender_password_label = QLabel("Password:")
        self.sender_password_input = QLineEdit()
        self.sender_password_input.setEchoMode(QLineEdit.EchoMode.Password)
        sender_password_layout.addWidget(sender_password_label)
        sender_password_layout.addWidget(self.sender_password_input)
        layout.addLayout(sender_password_layout)

        # Buttons
        buttons_layout = QHBoxLayout()
        save_button = QPushButton("Save")
        save_button.clicked.connect(self.save_email_config)
        cancel_button = QPushButton("Cancel")
        cancel_button.clicked.connect(self.reject)
        buttons_layout.addWidget(save_button)
        buttons_layout.addWidget(cancel_button)
        layout.addLayout(buttons_layout)

        self.setLayout(layout)

    def save_email_config(self):
        smtp_server = self.smtp_input.text().strip()
        port = self.port_input.value()
        sender_email = self.sender_email_input.text().strip()
        sender_password = self.sender_password_input.text().strip()

        if not all([smtp_server, port, sender_email, sender_password]):
            QMessageBox.warning(self, "Input Error", "All fields are required.")
            return

        # In a real application, securely store credentials
        # Here, we'll store them in a simple text file
        try:
            with open('email_config.txt', 'w') as f:
                f.write(f"{smtp_server}\n{port}\n{sender_email}\n{sender_password}")
            QMessageBox.information(self, "Success", "Email configuration saved.")
            self.accept()
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to save configuration: {e}")

    def get_email_config(self):
        smtp_server = self.smtp_input.text().strip()
        port = self.port_input.value()
        sender_email = self.sender_email_input.text().strip()
        sender_password = self.sender_password_input.text().strip()
        return smtp_server, port, sender_email, sender_password

# ---------------------------
# Report Dialog
# ---------------------------

class ReportDialog(QDialog):
    def __init__(self, db, report_type='Trial Balance', parent=None):
        super().__init__(parent)
        self.db = db
        self.report_type = report_type
        self.setWindowTitle(report_type)
        self.setMinimumSize(600, 400)
        self.setup_ui()

    def setup_ui(self):
        layout = QVBoxLayout()

        self.table = QTableWidget()
        layout.addWidget(self.table)

        self.setLayout(layout)
        self.generate_report()

    def generate_report(self):
        if self.report_type == 'Trial Balance':
            self.generate_trial_balance()
        elif self.report_type == 'Balance Sheet':
            self.generate_balance_sheet()
        elif self.report_type == 'Profit & Loss':
            self.generate_profit_and_loss()

    def generate_trial_balance(self):
        self.table.setColumnCount(4)
        self.table.setHorizontalHeaderLabels(['ID', 'Account Name', 'Debit (ZAR)', 'Credit (ZAR)'])

        accounts = self.db.fetchall('SELECT id, name, type FROM accounts ORDER BY type, name')
        self.table.setRowCount(len(accounts))

        total_debit = 0
        total_credit = 0

        for row, account in enumerate(accounts):
            acc_id, name, acc_type = account
            debit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE debit_account_id = ?', (acc_id,))[0] or 0
            credit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE credit_account_id = ?', (acc_id,))[0] or 0

            if acc_type in ['Assets', 'Expenses']:
                balance_debit = debit - credit
                balance_credit = 0
            else:
                balance_debit = 0
                balance_credit = credit - debit

            total_debit += balance_debit
            total_credit += balance_credit

            self.table.setItem(row, 0, QTableWidgetItem(str(acc_id)))
            self.table.setItem(row, 1, QTableWidgetItem(name))
            self.table.setItem(row, 2, QTableWidgetItem(f"{balance_debit:.2f}"))
            self.table.setItem(row, 3, QTableWidgetItem(f"{balance_credit:.2f}"))

        # Adding Total Row
        self.table.insertRow(self.table.rowCount())
        total_row = self.table.rowCount() - 1
        self.table.setItem(total_row, 0, QTableWidgetItem(""))
        self.table.setItem(total_row, 1, QTableWidgetItem("Total"))
        self.table.setItem(total_row, 2, QTableWidgetItem(f"{total_debit:.2f}"))
        self.table.setItem(total_row, 3, QTableWidgetItem(f"{total_credit:.2f}"))

        self.table.resizeColumnsToContents()

    def generate_balance_sheet(self):
        self.table.setColumnCount(3)
        self.table.setHorizontalHeaderLabels(['Category', 'Account Name', 'Amount (ZAR)'])

        categories = ['Assets', 'Liabilities', 'Equity']
        row_count = 0
        data = []

        total_liabilities = 0
        total_equity = 0
        total_assets = 0

        for category in categories:
            accounts = self.db.fetchall('SELECT name FROM accounts WHERE type = ?', (category,))
            for acc in accounts:
                name = acc[0]
                acc_id = self.db.fetchone('SELECT id FROM accounts WHERE name = ?', (name,))[0]
                if category == 'Assets':
                    debit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE debit_account_id = ?', (acc_id,))[0] or 0
                    credit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE credit_account_id = ?', (acc_id,))[0] or 0
                    balance = debit - credit
                    total_assets += balance
                elif category == 'Liabilities':
                    credit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE credit_account_id = ?', (acc_id,))[0] or 0
                    debit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE debit_account_id = ?', (acc_id,))[0] or 0
                    balance = credit - debit
                    total_liabilities += balance
                elif category == 'Equity':
                    credit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE credit_account_id = ?', (acc_id,))[0] or 0
                    debit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE debit_account_id = ?', (acc_id,))[0] or 0
                    balance = credit - debit
                    total_equity += balance

                data.append([category, name, f"{balance:.2f}"])
                row_count += 1

        self.table.setRowCount(row_count + 3)  # Additional rows for totals

        for row, entry in enumerate(data):
            category, name, amount = entry
            self.table.setItem(row, 0, QTableWidgetItem(category))
            self.table.setItem(row, 1, QTableWidgetItem(name))
            self.table.setItem(row, 2, QTableWidgetItem(amount))

        # Total Liabilities and Equity
        liab_row = row_count
        self.table.setItem(liab_row, 0, QTableWidgetItem("Total Liabilities"))
        self.table.setItem(liab_row, 1, QTableWidgetItem(""))
        self.table.setItem(liab_row, 2, QTableWidgetItem(f"{total_liabilities:.2f}"))

        equity_row = row_count + 1
        self.table.setItem(equity_row, 0, QTableWidgetItem("Total Equity"))
        self.table.setItem(equity_row, 1, QTableWidgetItem(""))
        self.table.setItem(equity_row, 2, QTableWidgetItem(f"{total_equity:.2f}"))

        # Total Assets
        assets_row = row_count + 2
        self.table.setItem(assets_row, 0, QTableWidgetItem("Total Assets"))
        self.table.setItem(assets_row, 1, QTableWidgetItem(""))
        self.table.setItem(assets_row, 2, QTableWidgetItem(f"{total_assets:.2f}"))

        self.table.resizeColumnsToContents()

    def generate_profit_and_loss(self):
        self.table.setColumnCount(3)
        self.table.setHorizontalHeaderLabels(['Category', 'Account Name', 'Amount (ZAR)'])

        categories = ['Income', 'Expenses']
        data = []
        total_income = 0
        total_expenses = 0

        for category in categories:
            accounts = self.db.fetchall('SELECT name FROM accounts WHERE type = ?', (category,))
            for acc in accounts:
                name = acc[0]
                acc_id = self.db.fetchone('SELECT id FROM accounts WHERE name = ?', (name,))[0]
                if category == 'Income':
                    credit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE credit_account_id = ?', (acc_id,))[0] or 0
                    debit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE debit_account_id = ?', (acc_id,))[0] or 0
                    balance = credit - debit
                    total_income += balance
                elif category == 'Expenses':
                    debit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE debit_account_id = ?', (acc_id,))[0] or 0
                    credit = self.db.fetchone('SELECT SUM(amount) FROM transactions WHERE credit_account_id = ?', (acc_id,))[0] or 0
                    balance = debit - credit
                    total_expenses += balance

                data.append([category, name, f"{balance:.2f}"])

        self.table.setRowCount(len(data) + 3)  # Additional rows for totals

        for row, entry in enumerate(data):
            category, name, amount = entry
            self.table.setItem(row, 0, QTableWidgetItem(category))
            self.table.setItem(row, 1, QTableWidgetItem(name))
            self.table.setItem(row, 2, QTableWidgetItem(amount))

        # Total Income
        income_row = len(data)
        self.table.setItem(income_row, 0, QTableWidgetItem("Total Income"))
        self.table.setItem(income_row, 1, QTableWidgetItem(""))
        self.table.setItem(income_row, 2, QTableWidgetItem(f"{total_income:.2f}"))

        # Total Expenses
        expenses_row = len(data) + 1
        self.table.setItem(expenses_row, 0, QTableWidgetItem("Total Expenses"))
        self.table.setItem(expenses_row, 1, QTableWidgetItem(""))
        self.table.setItem(expenses_row, 2, QTableWidgetItem(f"{total_expenses:.2f}"))

        # Net Profit
        net_profit = total_income - total_expenses
        net_profit_row = len(data) + 2
        self.table.setItem(net_profit_row, 0, QTableWidgetItem("Net Profit"))
        self.table.setItem(net_profit_row, 1, QTableWidgetItem(""))
        self.table.setItem(net_profit_row, 2, QTableWidgetItem(f"{net_profit:.2f}"))

        self.table.resizeColumnsToContents()

# ---------------------------
# Application Entry Point
# ---------------------------

def load_email_config():
    try:
        with open('email_config.txt', 'r') as f:
            lines = f.readlines()
            smtp_server = lines[0].strip()
            port = int(lines[1].strip())
            sender_email = lines[2].strip()
            sender_password = lines[3].strip()
            return smtp_server, port, sender_email, sender_password
    except:
        return None

def main():
    app = QApplication(sys.argv)
    window = MainWindow()

    # Load email configuration if exists
    email_config = load_email_config()
    if email_config:
        smtp_server, port, sender_email, sender_password = email_config
        window.email_sender = EmailSender(smtp_server, port, sender_email, sender_password)

    window.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()