Respan Dataset Explorer

Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.

5,167,182physical rows
86shards
0.00%qualified row coverage
0.00%qualified cell coverage
Random row JSON API

turns-00049.parquet:14133

83b20aa483350801e2ff6f7f
turn 7/8gpt-4o-mini-2024-07-18EnglishSouth Africa5101 words
degenerate_repetitionAbsentFinal dense release
USER
please revise and do not omit anything provide the full code snip.
ASSISTANT
Certainly! Below is the complete and corrected version of your code for the invoicing application, including all necessary components and functionalities, merged and structured properly without omitting anything.

### Complete Code for the Invoicing Application

```python
import sys
import json
import os
from datetime import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
import pandas as pd
import pdfplumber  # Added for PDF parsing
from PyQt6 import QtWidgets, QtGui, QtCore
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QTabWidget, QHBoxLayout,
    QLabel, QLineEdit, QPushButton, QMessageBox, QComboBox,
    QTextEdit, QListWidget, QListWidgetItem, QFileDialog, QSpinBox, QDialog
)

# Define file paths
CLIENTS_FILE = 'clients.json'
LINE_ITEMS_FILE = 'line_items.json'
INVOICES_DIR = 'invoices'
EMAIL_CONFIG_FILE = 'email_config.json'
TEMPLATES_FILE = 'templates.json'
COMPANY_DETAILS_FILE = 'company_details.json'  # New file for company details
INVOICES_METADATA_FILE = 'invoices.json'  # New file for invoice metadata

# Ensure necessary directories exist
os.makedirs(INVOICES_DIR, exist_ok=True)

# Function to load data from JSON
def load_json(file_path):
    if os.path.exists(file_path):
        with open(file_path, 'r') as f:
            return json.load(f)
    return {}

# Function to save data to JSON
def save_json(data, file_path):
    with open(file_path, 'w') as f:
        json.dump(data, f, indent=4)

# GUI Application
class QuickBooksReplacement(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.clients = load_json(CLIENTS_FILE)
        self.line_items_data = load_json(LINE_ITEMS_FILE)
        self.email_config = load_json(EMAIL_CONFIG_FILE)
        self.templates = load_json(TEMPLATES_FILE)
        self.company_details = load_json(COMPANY_DETAILS_FILE)  # Initialize company details
        self.invoices_metadata = load_json(INVOICES_METADATA_FILE)  # Initialize invoice metadata
        self.invoice_counter = self.get_next_invoice_number()  # Initialize invoice counter
        self.init_ui()

    def get_next_invoice_number(self):
        """Gets the next available invoice number."""
        if os.path.exists("invoice_counter.txt"):
            with open("invoice_counter.txt", "r") as f:
                try:
                    counter = int(f.read())
                    return counter + 1
                except ValueError:
                    return 1
        else:
            return 1

    def update_invoice_counter(self):
        """Updates the invoice counter file."""
        with open("invoice_counter.txt", "w") as f:
            f.write(str(self.invoice_counter))

    def init_ui(self):
        self.setWindowTitle("QuickBooks Replacement Tool")
        self.setGeometry(100, 100, 1400, 900)  # Increased width and height for better layout
        layout = QVBoxLayout()

        # Apply Enhanced Dark Theme
        self.apply_dark_theme()

        # Tab Widget
        self.tabs = QTabWidget()
        layout.addWidget(self.tabs)

        # Tabs
        self.client_tab = ClientManagementTab(self)
        self.line_item_tab = LineItemManagementTab(self)
        self.invoice_tab = InvoiceCreationTab(self)
        self.manage_invoices_tab = ManageInvoicesTab(self)
        self.financial_statements_tab = FinancialStatementsTab(self)
        self.email_config_tab = EmailConfigTab(self)
        self.company_details_tab = CompanyDetailsTab(self)
        self.templates_tab = TemplateManagementTab(self)

        self.tabs.addTab(self.client_tab, QtGui.QIcon.fromTheme("user-group"), "Clients")
        self.tabs.addTab(self.line_item_tab, QtGui.QIcon.fromTheme("list-add"), "Line Items")
        self.tabs.addTab(self.invoice_tab, QtGui.QIcon.fromTheme("document-new"), "Create Invoice")
        self.tabs.addTab(self.manage_invoices_tab, QtGui.QIcon.fromTheme("folder"), "Manage Invoices")
        self.tabs.addTab(self.financial_statements_tab, QtGui.QIcon.fromTheme("view-summary"), "Financial Statements")
        self.tabs.addTab(self.email_config_tab, QtGui.QIcon.fromTheme("email-send"), "Email Settings")
        self.tabs.addTab(self.company_details_tab, QtGui.QIcon.fromTheme("office-address"), "Company Details")
        self.tabs.addTab(self.templates_tab, QtGui.QIcon.fromTheme("format-paint"), "Templates")

        # Log Message Area
        self.log_area = QTextEdit(self)
        self.log_area.setReadOnly(True)
        self.log_area.setStyleSheet("""
            background-color: #1E1E1E;
            color: #ffffff;
            font-family: Consolas;
            font-size: 12px;
            border: 1px solid #555555;
        """)
        log_layout = QVBoxLayout()
        log_layout.addWidget(QLabel("Log:"))
        log_layout.addWidget(self.log_area)
        layout.addLayout(log_layout)

        self.setLayout(layout)

    def apply_dark_theme(self):
        dark_stylesheet = """
            /* General Widget Styles */
            QWidget {
                background-color: #121212;
                color: #ffffff;
                font-family: "Segoe UI", sans-serif;
                font-size: 14px;
            }
            QLabel {
                color: #ffffff;
            }
            QLineEdit, QTextEdit, QComboBox, QListWidget, QListWidgetItem, QSpinBox, QPushButton {
                background-color: #1E1E1E;
                border: 1px solid #555555;
                color: #ffffff;
                padding: 5px;
                border-radius: 4px;
            }
            QLineEdit:focus, QTextEdit:focus, QComboBox:focus, QListWidget:focus, QSpinBox:focus, QPushButton:focus {
                border: 2px solid #1E90FF;
            }
            QPushButton {
                background-color: #2D2D30;
            }
            QPushButton:hover {
                background-color: #3E3E42;
            }
            QPushButton:pressed {
                background-color: #5A5A5E;
            }
            QTabWidget::pane {
                border: 1px solid #555555;
            }
            QTabBar::tab {
                background: #2D2D30;
                border: 1px solid #555555;
                padding: 10px;
                margin: 2px;
                color: #ffffff;
                border-radius: 4px;
            }
            QTabBar::tab:selected {
                background: #1E90FF;
                color: #ffffff;
            }
            QMenuBar, QMenu {
                background-color: #121212;
                color: #ffffff;
                font-size: 14px;
            }
            QMenu::item:selected {
                background-color: #3E3E42;
            }
            QListWidget::item:selected {
                background-color: #1E90FF;
                color: #ffffff;
            }
            QHeaderView::section {
                background-color: #2D2D30;
                color: #ffffff;
                padding: 4px;
                border: 1px solid #555555;
            }
            QDialog {
                background-color: #121212;
                color: #ffffff;
            }
            QDialog QPushButton {
                min-width: 80px;
            }
        """
        self.setStyleSheet(dark_stylesheet)

    def log_message(self, message):
        """Logs message to the text area."""
        self.log_area.append(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {message}")

# Client Management Tab
class ClientManagementTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui__()

    def init_ui(self):
        layout = QVBoxLayout()

        # Form to add/edit client
        form_group = QtWidgets.QGroupBox("Add New Client")
        form_layout = QHBoxLayout()
        self.client_name_input = QLineEdit(self)
        self.client_name_input.setPlaceholderText("Client Name")
        self.client_email_input = QLineEdit(self)
        self.client_email_input.setPlaceholderText("Client Email")
        self.client_address_input = QLineEdit(self)
        self.client_address_input.setPlaceholderText("Client Address")
        self.client_vat_input = QLineEdit(self)  # VAT Number Field
        self.client_vat_input.setPlaceholderText("Client VAT Number")
        self.add_client_button = QPushButton("Add Client")
        self.add_client_button.clicked.connect(self.add_client)

        form_layout.addWidget(QLabel("Name:"))
        form_layout.addWidget(self.client_name_input)
        form_layout.addWidget(QLabel("Email:"))
        form_layout.addWidget(self.client_email_input)
        form_layout.addWidget(QLabel("Address:"))
        form_layout.addWidget(self.client_address_input)
        form_layout.addWidget(QLabel("VAT Number:"))  # Label for VAT
        form_layout.addWidget(self.client_vat_input)    # VAT Input
        form_layout.addWidget(self.add_client_button)

        form_group.setLayout(form_layout)
        layout.addWidget(form_group)

        # List of clients
        self.clients_list = QListWidget()
        self.load_clients()
        layout.addWidget(QLabel("Existing Clients:"))
        layout.addWidget(self.clients_list)

        # Buttons to edit and delete clients
        btn_layout = QHBoxLayout()
        self.edit_client_button = QPushButton("Edit Selected")
        self.edit_client_button.clicked.connect(self.edit_client)
        self.delete_client_button = QPushButton("Delete Selected")
        self.delete_client_button.clicked.connect(self.delete_client)
        btn_layout.addWidget(self.edit_client_button)
        btn_layout.addWidget(self.delete_client_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_clients(self):
        self.clients_list.clear()
        for client in self.parent.clients.keys():
            item = QListWidgetItem(client)
            self.clients_list.addItem(item)

    def add_client(self):
        name = self.client_name_input.text().strip()
        email = self.client_email_input.text().strip()
        address = self.client_address_input.text().strip()
        vat = self.client_vat_input.text().strip()  # Retrieve VAT number

        if not name or not email or not address:
            QMessageBox.warning(self, "Input Error", "Please fill in all required fields.")
            return

        if name in self.parent.clients:
            QMessageBox.warning(self, "Duplicate Client", "Client already exists.")
            return

        self.parent.clients[name] = {
            "email": email,
            "address": address,
            "vat_number": vat  # Store VAT number
        }
        save_json(self.parent.clients, CLIENTS_FILE)
        self.parent.log_message(f"Client '{name}' added.")
        self.load_clients()
        self.client_name_input.clear()
        self.client_email_input.clear()
        self.client_address_input.clear()
        self.client_vat_input.clear()

    def edit_client(self):
        selected = self.clients_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a client to edit.")
            return
        name = selected.text()
        client_data = self.parent.clients[name]

        # Dialog to edit client
        dialog = EditClientDialog(name, client_data, self.parent)
        if dialog.exec():
            new_name, new_email, new_address, new_vat = dialog.get_data()
            if new_name != name and new_name in self.parent.clients:
                QMessageBox.warning(self, "Duplicate Client", "Client name already exists.")
                return
            # Update client
            del self.parent.clients[name]
            self.parent.clients[new_name] = {
                "email": new_email,
                "address": new_address,
                "vat_number": new_vat  # Update VAT number
            }
            save_json(self.parent.clients, CLIENTS_FILE)
            self.parent.log_message(f"Client '{name}' updated to '{new_name}'.")
            self.load_clients()

    def delete_client(self):
        selected = self.clients_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a client to delete.")
            return
        name = selected.text()
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete client '{name}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            del self.parent.clients[name]
            save_json(self.parent.clients, CLIENTS_FILE)
            self.parent.log_message(f"Client '{name}' deleted.")
            self.load_clients()

class EditClientDialog(QDialog):
    def __init__(self, name, data, parent):
        super().__init__()
        self.setWindowTitle("Edit Client")
        self.parent = parent
        self.name = name
        self.data = data
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()

        self.name_input = QLineEdit(self)
        self.name_input.setText(self.name)
        self.email_input = QLineEdit(self)
        self.email_input.setText(self.data.get("email", ""))
        self.address_input = QLineEdit(self)
        self.address_input.setText(self.data.get("address", ""))
        self.vat_input = QLineEdit(self)  # VAT Number Field
        self.vat_input.setText(self.data.get("vat_number", ""))  # Retrieve VAT number

        self.layout.addWidget(QLabel("Name:"))
        self.layout.addWidget(self.name_input)
        self.layout.addWidget(QLabel("Email:"))
        self.layout.addWidget(self.email_input)
        self.layout.addWidget(QLabel("Address:"))
        self.layout.addWidget(self.address_input)
        self.layout.addWidget(QLabel("VAT Number:"))  # Label for VAT
        self.layout.addWidget(self.vat_input)          # VAT Input

        self.button_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.layout.addWidget(self.button_box)

        self.setLayout(self.layout)

    def get_data(self):
        return (
            self.name_input.text().strip(),
            self.email_input.text().strip(),
            self.address_input.text().strip(),
            self.vat_input.text().strip()  # Return VAT number
        )

# Line Item Management Tab
class LineItemManagementTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Form to add/edit line item
        form_group = QtWidgets.QGroupBox("Add New Line Item")
        form_layout = QHBoxLayout()
        self.item_name_input = QLineEdit(self)
        self.item_name_input.setPlaceholderText("Item Name")
        self.item_price_input = QLineEdit(self)
        self.item_price_input.setPlaceholderText("Item Price")
        self.add_item_button = QPushButton("Add Item")
        self.add_item_button.clicked.connect(self.add_line_item)

        form_layout.addWidget(QLabel("Name:"))
        form_layout.addWidget(self.item_name_input)
        form_layout.addWidget(QLabel("Price:"))
        form_layout.addWidget(self.item_price_input)
        form_layout.addWidget(self.add_item_button)

        form_group.setLayout(form_layout)
        layout.addWidget(form_group)

        # Supplier Section (Retained for internal management but not displayed in invoices)
        supplier_group = QtWidgets.QGroupBox("Supplier Information (Internal Use)")
        supplier_layout = QHBoxLayout()
        self.supplier_input = QLineEdit(self)
        self.supplier_input.setPlaceholderText("Supplier Name")
        supplier_layout.addWidget(QLabel("Supplier:"))
        supplier_layout.addWidget(self.supplier_input)
        supplier_group.setLayout(supplier_layout)
        layout.addWidget(supplier_group)

        # List of line items
        self.items_list = QListWidget()
        self.load_line_items()
        layout.addWidget(QLabel("Existing Line Items:"))
        layout.addWidget(self.items_list)

        # Buttons to edit, delete, and import line items
        btn_layout = QHBoxLayout()
        self.edit_item_button = QPushButton("Edit Selected")
        self.edit_item_button.clicked.connect(self.edit_line_item)
        self.delete_item_button = QPushButton("Delete Selected")
        self.delete_item_button.clicked.connect(self.delete_line_item)
        self.import_pdf_button = QPushButton("Import from PDF")  # New Import Button
        self.import_pdf_button.clicked.connect(self.import_from_pdf)
        btn_layout.addWidget(self.edit_item_button)
        btn_layout.addWidget(self.delete_item_button)
        btn_layout.addWidget(self.import_pdf_button)  # Add Import Button to Layout
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_line_items(self):
        self.items_list.clear()
        for item, details in self.parent.line_items_data.items():
            price = details.get('price', 0.0)
            self.items_list.addItem(f"{item} - Price: R{price:.2f}")

    def add_line_item(self):
        name = self.item_name_input.text().strip()
        price = self.item_price_input.text().strip()
        supplier = self.supplier_input.text().strip()  # Optional, for internal use

        if not name or not price:
            QMessageBox.warning(self, "Input Error", "Please fill in the Item Name and Price.")
            return

        try:
            price = float(price)
            if price < 0:
                raise ValueError
        except ValueError:
            QMessageBox.warning(self, "Input Error", "Please enter a valid positive price.")
            return

        if name in self.parent.line_items_data:
            QMessageBox.warning(self, "Duplicate Item", "Line item already exists.")
            return

        self.parent.line_items_data[name] = {"price": price, "supplier": supplier}
        save_json(self.parent.line_items_data, LINE_ITEMS_FILE)
        self.parent.log_message(f"Line item '{name}' added.")
        self.load_line_items()
        self.item_name_input.clear()
        self.item_price_input.clear()
        self.supplier_input.clear()

    def edit_line_item(self):
        selected = self.items_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a line item to edit.")
            return
        item_text = selected.text()
        name = item_text.split(" - Price: ")[0]
        item_data = self.parent.line_items_data[name]

        # Dialog to edit line item
        dialog = EditLineItemDialog(name, item_data, self.parent)
        if dialog.exec():
            new_name, new_price, new_supplier = dialog.get_data()
            if new_name != name and new_name in self.parent.line_items_data:
                QMessageBox.warning(self, "Duplicate Item", "Line item name already exists.")
                return
            try:
                new_price = float(new_price)
                if new_price < 0:
                    raise ValueError
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid positive price.")
                return
            # Update line item
            del self.parent.line_items_data[name]
            self.parent.line_items_data[new_name] = {"price": new_price, "supplier": new_supplier}
            save_json(self.parent.line_items_data, LINE_ITEMS_FILE)
            self.parent.log_message(f"Line item '{name}' updated to '{new_name}'.")
            self.load_line_items()

    def delete_line_item(self):
        selected = self.items_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select a line item to delete.")
            return
        item_text = selected.text()
        name = item_text.split(" - Price: ")[0]
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete line item '{name}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            del self.parent.line_items_data[name]
            save_json(self.parent.line_items_data, LINE_ITEMS_FILE)
            self.parent.log_message(f"Line item '{name}' deleted.")
            self.load_line_items()

    def import_from_pdf(self):
        """Imports line items from a selected PDF file."""
        file_path, _ = QFileDialog.getOpenFileName(self, "Select PDF", "", "PDF Files (*.pdf)")
        if not file_path:
            return

        try:
            with pdfplumber.open(file_path) as pdf:
                imported_items = 0
                for page in pdf.pages:
                    tables = page.extract_tables()
                    for table in tables:
                        if not table:
                            continue
                        headers = table[0]
                        if all(header in headers for header in ["Item", "Quantity", "Price", "Total"]):
                            item_idx = headers.index("Item")
                            quantity_idx = headers.index("Quantity")
                            price_idx = headers.index("Price")
                            total_idx = headers.index("Total")

                            for row in table[1:]:
                                if len(row) < 4:
                                    continue
                                item_name = row[item_idx].strip()
                                quantity = row[quantity_idx].strip()
                                price = row[price_idx].strip().replace('R', '').replace(',', '')
                                total = row[total_idx].strip().replace('R', '').replace(',', '')

                                if not item_name:
                                    continue
                                try:
                                    quantity = int(quantity)
                                    price = float(price)
                                    total = float(total)
                                except ValueError:
                                    self.parent.log_message(f"Invalid data in PDF for item '{item_name}'. Skipping.")
                                    continue

                                # Add to line items
                                if item_name not in self.parent.line_items_data:
                                    self.parent.line_items_data[item_name] = {
                                        "price": price,
                                        "supplier": ""  # Supplier not included as per requirement
                                    }
                                    imported_items += 1
                                else:
                                    self.parent.log_message(f"Line item '{item_name}' already exists. Skipping import.")

                if imported_items > 0:
                    save_json(self.parent.line_items_data, LINE_ITEMS_FILE)
                    self.parent.log_message(f"Imported {imported_items} line items from PDF.")
                    QMessageBox.information(self, "Import Successful", f"Imported {imported_items} line items from the PDF.")
                    self.load_line_items()
                else:
                    QMessageBox.information(self, "Import Result", "No new line items were imported from the PDF.")

        except Exception as e:
            QMessageBox.warning(self, "Import Error", f"Failed to import from PDF: {e}")
            self.parent.log_message(f"Failed to import from PDF '{file_path}': {e}")

# Dialog for editing a line item
class EditLineItemDialog(QDialog):
    def __init__(self, name, data, parent):
        super().__init__()
        self.setWindowTitle("Edit Line Item")
        self.parent = parent
        self.name = name
        self.data = data
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()

        self.name_input = QLineEdit(self)
        self.name_input.setText(self.name)
        self.price_input = QLineEdit(self)
        self.price_input.setText(str(self.data["price"]))

        self.layout.addWidget(QLabel("Name:"))
        self.layout.addWidget(self.name_input)
        self.layout.addWidget(QLabel("Price:"))
        self.layout.addWidget(self.price_input)

        self.button_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.layout.addWidget(self.button_box)

        self.setLayout(self.layout)

    def get_data(self):
        return (
            self.name_input.text().strip(),
            self.price_input.text().strip()
        )

# Invoice Creation Tab
class InvoiceCreationTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.line_items = []
        self.selected_template = None  # For template usage
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Template Selection
        template_layout = QHBoxLayout()
        self.template_combo = QComboBox(self)
        self.load_templates()
        self.template_combo.currentIndexChanged.connect(self.change_template)
        template_layout.addWidget(QLabel("Select Template:"))
        template_layout.addWidget(self.template_combo)
        layout.addLayout(template_layout)

        # Client selection
        client_layout = QHBoxLayout()
        self.client_combo = QComboBox(self)
        self.load_clients()
        client_layout.addWidget(QLabel("Select Client:"))
        client_layout.addWidget(self.client_combo)
        layout.addLayout(client_layout)

        # VAT Number Display (Read-only)
        vat_display_layout = QHBoxLayout()
        self.vat_display = QLineEdit(self)
        self.vat_display.setReadOnly(True)
        vat_display_layout.addWidget(QLabel("Client VAT Number:"))
        vat_display_layout.addWidget(self.vat_display)
        layout.addLayout(vat_display_layout)

        # Update VAT display when client changes
        self.client_combo.currentIndexChanged.connect(self.update_vat_display)
        self.update_vat_display()  # Initialize VAT display

        # Line Items selection
        line_item_layout = QHBoxLayout()
        self.item_combo = QComboBox(self)
        self.load_line_items()
        self.quantity_input = QSpinBox(self)
        self.quantity_input.setMinimum(1)
        self.quantity_input.setValue(1)
        self.add_item_button = QPushButton("Add Item")
        self.add_item_button.clicked.connect(self.add_line_item)
        line_item_layout.addWidget(QLabel("Item:"))
        line_item_layout.addWidget(self.item_combo)
        line_item_layout.addWidget(QLabel("Quantity:"))
        line_item_layout.addWidget(self.quantity_input)
        line_item_layout.addWidget(self.add_item_button)
        layout.addLayout(line_item_layout)

        # List to display added line items without supplier info
        self.added_items_list = QListWidget()
        self.added_items_list.setStyleSheet("""
            QListWidget {
                background-color: #1E1E1E;
                color: #ffffff;
                border: 1px solid #555555;
            }
            QListWidget::item {
                padding: 5px;
            }
        """)
        layout.addWidget(QLabel("Added Line Items:"))
        layout.addWidget(self.added_items_list)

        # Discounts and Taxes
        discounts_taxes_layout = QHBoxLayout()
        self.discount_input = QLineEdit(self)
        self.discount_input.setPlaceholderText("Discount (%)")
        self.tax_rate_input = QLineEdit(self)
        self.tax_rate_input.setPlaceholderText("Tax Rate (%)")
        discounts_taxes_layout.addWidget(QLabel("Discount (%):"))
        discounts_taxes_layout.addWidget(self.discount_input)
        discounts_taxes_layout.addWidget(QLabel("Tax Rate (%):"))
        discounts_taxes_layout.addWidget(self.tax_rate_input)
        layout.addLayout(discounts_taxes_layout)

        # Output Format Selection
        output_layout = QHBoxLayout()
        self.output_format_combo = QComboBox(self)
        self.output_format_combo.addItems(["PDF", "Excel"])
        output_layout.addWidget(QLabel("Output Format:"))
        output_layout.addWidget(self.output_format_combo)
        layout.addLayout(output_layout)

        # Button to create invoice
        self.create_invoice_button = QPushButton("Create Invoice")
        self.create_invoice_button.clicked.connect(self.create_invoice)
        layout.addWidget(self.create_invoice_button)

        self.setLayout(layout)

    def load_templates(self):
        self.template_combo.clear()
        templates_list = list(self.parent.templates.keys())
        self.template_combo.addItems(["Default"] + templates_list)
        self.selected_template = "Default"

    def change_template(self, index):
        selected = self.template_combo.currentText()
        if selected == "Default":
            self.selected_template = None
        else:
            self.selected_template = selected

    def load_clients(self):
        self.client_combo.clear()
        self.client_combo.addItems(list(self.parent.clients.keys()))

    def load_line_items(self):
        self.item_combo.clear()
        self.item_combo.addItems(list(self.parent.line_items_data.keys()))

    def add_line_item(self):
        item_name = self.item_combo.currentText()
        quantity = self.quantity_input.value()
        if not item_name:
            QMessageBox.warning(self, "Selection Error", "Please select a line item.")
            return
        price = self.parent.line_items_data[item_name]['price']
        total = price * quantity
        self.line_items.append({
            "item": item_name,
            "quantity": quantity,
            "price": price,
            "total": total
        })
        self.update_added_items_list()

    def update_added_items_list(self):
        self.added_items_list.clear()
        for idx, item in enumerate(self.line_items, 1):
            self.added_items_list.addItem(
                f"{idx}. {item['item']} - Qty: {item['quantity']} - Price: R{item['price']:.2f} - Total: R{item['total']:.2f}"
            )

    def update_vat_display(self):
        client_name = self.client_combo.currentText()
        vat_number = self.parent.clients.get(client_name, {}).get("vat_number", "")
        self.vat_display.setText(vat_number)

    def create_invoice(self):
        client_name = self.client_combo.currentText()
        vat_number = self.parent.clients.get(client_name, {}).get("vat_number", "")
        discount = self.discount_input.text().strip()
        tax_rate = self.tax_rate_input.text().strip()

        if not client_name:
            QMessageBox.warning(self, "Input Error", "Please select a client.")
            return

        if not self.line_items:
            QMessageBox.warning(self, "Input Error", "Please add at least one line item.")
            return

        # Calculate totals
        subtotal = sum(item['total'] for item in self.line_items)
        discount_amount = 0
        if discount:
            try:
                discount_percentage = float(discount)
                discount_amount = subtotal * (discount_percentage / 100)
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid discount percentage.")
                return

        subtotal_after_discount = subtotal - discount_amount

        tax_amount = 0
        if tax_rate:
            try:
                tax_percentage = float(tax_rate)
                tax_amount = subtotal_after_discount * (tax_percentage / 100)
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid tax rate percentage.")
                return

        total = subtotal_after_discount + tax_amount

        invoice_date = datetime.now().strftime("%Y-%m-%d")  # Automatic date filling
        client_address = self.parent.clients[client_name]["address"]
        invoice_filename = f"{client_name.replace(' ', '_')}_invoice_{self.parent.invoice_counter}"

        # Prepare DataFrame without supplier info
        df = pd.DataFrame([
            {"Item": item['item'], "Quantity": item['quantity'], "Price (ZAR)": item['price'], "Total (ZAR)": item['total']}
            for item in self.line_items
        ])

        output_format = self.output_format_combo.currentText()
        success = False
        if output_format == "PDF":
            success = self.create_pdf(client_name, vat_number, invoice_date, client_address, df, invoice_filename, subtotal, discount_amount, tax_amount, total)
        elif output_format == "Excel":
            success = self.create_excel(client_name, vat_number, invoice_date, client_address, df, invoice_filename, subtotal, discount_amount, tax_amount, total)
        else:
            QMessageBox.warning(self, "Format Error", "Please select a valid output format.")
            return

        if success:
            # Log invoice metadata
            invoice_metadata = {
                "invoice_number": self.parent.invoice_counter - 1,
                "client_name": client_name,
                "date": invoice_date,
                "total": total,
                "status": "Unpaid",
                "filename": f"{invoice_filename}.{output_format.lower()}"
            }
            self.parent.invoices_metadata[str(self.parent.invoice_counter - 1)] = invoice_metadata
            save_json(self.parent.invoices_metadata, INVOICES_METADATA_FILE)

            # Reset form
            self.line_items = []
            self.update_added_items_list()
            self.parent.invoice_counter += 1  # Increment invoice counter after successful creation
            self.parent.update_invoice_counter()

    def apply_template_attributes(self, c, template, invoice_details):
        """Applies template attributes like header, footer, and logo to the PDF."""
        if 'header_text' in template:
            c.setFont("Helvetica-Bold", 16)
            c.drawString(50, invoice_details['height'] - 50, template['header_text'])

        if 'footer_text' in template:
            c.setFont("Helvetica", 10)
            c.drawString(50, 30, template['footer_text'])

        if 'logo_path' in template and template['logo_path']:
            if os.path.exists(template['logo_path']):
                c.drawImage(template['logo_path'], invoice_details['width'] - 150, invoice_details['height'] - 100, width=100, height=50)

    def create_pdf(self, client, vat, invoice_date, client_address, df, invoice_filename, subtotal, discount, tax, total):
        """Generate PDF invoice."""
        pdf_filename = os.path.join(INVOICES_DIR, f"{invoice_filename}.pdf")
        template = self.get_selected_template()

        c = canvas.Canvas(pdf_filename, pagesize=letter)
        width, height = letter

        # Apply Template Attributes
        invoice_details = {'width': width, 'height': height}
        self.apply_template_attributes(c, template, invoice_details)

        # Company Details
        company_name = self.parent.company_details.get("company_name", "Your Company Name")
        company_address = self.parent.company_details.get("company_address", "Your Company Address")
        company_email = self.parent.company_details.get("company_email", "your.email@example.com")
        company_phone = self.parent.company_details.get("company_phone", "123-456-7890")
        # Optional: Company logo
        if self.parent.company_details.get("company_logo_path", "") and os.path.exists(self.parent.company_details["company_logo_path"]):
            c.drawImage(self.parent.company_details["company_logo_path"], 50, height - 100, width=100, height=50)

        c.setFont("Helvetica-Bold", 14)
        c.drawString(50, height - 120, company_name)
        c.setFont("Helvetica", 12)
        c.drawString(50, height - 140, company_address)
        c.drawString(50, height - 160, f"Email: {company_email}")
        c.drawString(50, height - 180, f"Phone: {company_phone}")

        # Invoice Header
        c.setFont("Helvetica-Bold", 20)
        c.drawString(400, height - 120, "INVOICE")  # Adjusted position

        # Invoice Information
        c.setFont("Helvetica", 12)
        c.drawString(400, height - 150, f"Invoice Number: {self.parent.invoice_counter - 1}")  # Displaying counter
        c.drawString(400, height - 170, f"Date: {invoice_date}")

        # Client Information
        c.setFont("Helvetica-Bold", 12)
        c.drawString(50, height - 220, "Bill To:")
        c.setFont("Helvetica", 12)
        c.drawString(50, height - 240, f"{client}")
        c.drawString(50, height - 260, f"Address: {client_address}")
        if vat:
            c.drawString(50, height - 280, f"VAT Number: {vat}")

        # Draw a box around client information
        c.rect(40, height - 290, 500, 60, stroke=1, fill=0)

        # Table Headers
        c.setFont("Helvetica-Bold", 12)
        y = height - 350
        c.drawString(50, y, "Item")
        c.drawString(250, y, "Quantity")
        c.drawString(350, y, "Price (ZAR)")
        c.drawString(450, y, "Total (ZAR)")
        y -= 20
        c.line(50, y, 550, y)

        # Table Content
        c.setFont("Helvetica", 12)
        for index, row in df.iterrows():
            y -= 20
            if y < 150:
                c.showPage()
                y = height - 50
                # Re-apply template on new page
                self.apply_template_attributes(c, template, invoice_details)
                # Re-draw company details
                c.setFont("Helvetica-Bold", 14)
                c.drawString(50, height - 120, company_name)
                c.setFont("Helvetica", 12)
                c.drawString(50, height - 140, company_address)
                c.drawString(50, height - 160, f"Email: {company_email}")
                c.drawString(50, height - 180, f"Phone: {company_phone}")
                # Re-draw invoice header
                c.setFont("Helvetica-Bold", 20)
                c.drawString(400, height - 120, "INVOICE")
                c.setFont("Helvetica", 12)
                c.drawString(400, height - 150, f"Invoice Number: {self.parent.invoice_counter - 1}")
                c.drawString(400, height - 170, f"Date: {invoice_date}")
                # Re-draw client information
                c.setFont("Helvetica-Bold", 12)
                c.drawString(50, height - 220, "Bill To:")
                c.setFont("Helvetica", 12)
                c.drawString(50, height - 240, f"{client}")
                c.drawString(50, height - 260, f"Address: {client_address}")
                if vat:
                    c.drawString(50, height - 280, f"VAT Number: {vat}")

                # Re-draw table headers
                c.setFont("Helvetica-Bold", 12)
                y = height - 300
                c.drawString(50, y, "Item")
                c.drawString(250, y, "Quantity")
                c.drawString(350, y, "Price (ZAR)")
                c.drawString(450, y, "Total (ZAR)")
                y -= 20
                c.line(50, y, 550, y)

            c.drawString(50, y, str(row['Item']))
            c.drawString(250, y, str(row['Quantity']))
            c.drawString(350, y, f"R{row['Price (ZAR)']:.2f}")
            c.drawString(450, y, f"R{row['Total (ZAR)']:.2f}")

            # Draw lines around each row
            c.line(45, y - 5, 555, y - 5)

        # Totals
        y -= 40
        c.setFont("Helvetica-Bold", 12)
        c.drawString(350, y, "Subtotal:")
        c.drawString(450, y, f"R{subtotal:.2f}")
        y -= 20
        if discount > 0:
            c.drawString(350, y, "Discount:")
            c.drawString(450, y, f"R{-discount:.2f}")
            y -= 20
        if tax > 0:
            c.drawString(350, y, "Tax:")
            c.drawString(450, y, f"R{tax:.2f}")
            y -= 20
        c.drawString(350, y, "Total:")
        c.drawString(450, y, f"R{total:.2f}")

        # Draw a box around the totals
        c.rect(340, y - 10, 220, 60, stroke=1, fill=0)

        # Footnote
        c.setFont("Helvetica-Oblique", 10)
        c.drawString(50, 30, "Thank you for your business.")  # Added footnote

        c.save()
        self.parent.log_message(f"Invoice saved as {pdf_filename}")
        self.parent.manage_invoices_tab.load_invoices()
        self.parent.financial_statements_tab.load_financial_statements()  # Refresh financial statements
        self.send_email_invoice(client, pdf_filename)

    def create_excel(self, client, vat, invoice_date, client_address, df, invoice_filename, subtotal, discount, tax, total):
        """Generate Excel invoice."""
        try:
            excel_filename = os.path.join(INVOICES_DIR, f"{invoice_filename}.xlsx")
            template = self.get_selected_template()

            writer = pd.ExcelWriter(excel_filename, engine='openpyxl')
            df.to_excel(writer, index=False, sheet_name='Invoice')

            # Access the workbook and worksheet
            workbook = writer.book
            worksheet = writer.sheets['Invoice']

            # Apply Template Attributes (basic - headers)
            current_row = 1
            if 'header_text' in template:
                worksheet.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=4)
                cell = worksheet.cell(row=current_row, column=1)
                cell.value = template['header_text']
                cell.font = cell.font.copy(bold=True, size=16)
                current_row += 1

            # Company Details
            company_name = self.parent.company_details.get("company_name", "Your Company Name")
            company_address = self.parent.company_details.get("company_address", "Your Company Address")
            company_email = self.parent.company_details.get("company_email", "your.email@example.com")
            company_phone = self.parent.company_details.get("company_phone", "123-456-7890")
            company_logo = self.parent.company_details.get("company_logo_path", "")

            if company_logo and os.path.exists(company_logo):
                # Insert company logo if available
                img = QtGui.QPixmap(company_logo)
                img.save('temp_logo.png')  # Save temporarily
                from openpyxl.drawing.image import Image as XLImage
                img_excel = XLImage('temp_logo.png')
                img_excel.anchor = 'A1'
                worksheet.add_image(img_excel, 'A1')
                current_row += 2  # Adjust row if logo is added

            worksheet.cell(row=current_row, column=1, value=company_name)
            worksheet.cell(row=current_row, column=2, value=f"Invoice Number: {self.parent.invoice_counter - 1}")
            current_row += 1
            worksheet.cell(row=current_row, column=1, value=company_address)
            worksheet.cell(row=current_row, column=2, value=f"Date: {invoice_date}")
            current_row += 1
            worksheet.cell(row=current_row, column=1, value=f"Email: {company_email}")
            worksheet.cell(row=current_row, column=2, value=f"Client: {client}")
            current_row += 1
            worksheet.cell(row=current_row, column=1, value=f"Phone: {company_phone}")
            worksheet.cell(row=current_row, column=2, value=f"Address: {client_address}")
            current_row += 1
            if vat:
                worksheet.cell(row=current_row, column=1, value=f"VAT Number: {vat}")
                current_row += 1

            # Add totals
            start_row = len(df) + current_row + 2
            worksheet.cell(row=start_row, column=3, value="Subtotal:")
            worksheet.cell(row=start_row, column=4, value=subtotal)
            if discount > 0:
                worksheet.cell(row=start_row + 1, column=3, value="Discount:")
                worksheet.cell(row=start_row + 1, column=4, value=-discount)
            if tax > 0:
                worksheet.cell(row=start_row + 2, column=3, value="Tax:")
                worksheet.cell(row=start_row + 2, column=4, value=tax)
            worksheet.cell(row=start_row + 3, column=3, value="Total:")
            worksheet.cell(row=start_row + 3, column=4, value=total)

            # Add footnote
            worksheet.cell(row=start_row + 5, column=1, value="Thank you for your business.")

            writer.save()
            os.remove('temp_logo.png')  # Clean up temporary logo
            self.parent.log_message(f"Invoice saved as {excel_filename}")
            self.parent.manage_invoices_tab.load_invoices()
            self.parent.financial_statements_tab.load_financial_statements()  # Refresh financial statements
            self.send_email_invoice(client, excel_filename)
            return True
        except Exception as e:
            QMessageBox.warning(self, "Excel Generation Error", f"Failed to generate Excel invoice: {e}")
            self.parent.log_message(f"Failed to generate Excel invoice '{excel_filename}': {e}")
            return False

    def get_selected_template(self):
        """Retrieves the selected template's attributes."""
        if self.selected_template and self.selected_template in self.parent.templates:
            return self.parent.templates[self.selected_template]
        return {}

# Manage Invoices Tab
class ManageInvoicesTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        self.invoices_list = QListWidget()
        self.invoices_list.setStyleSheet("""
            QListWidget {
                background-color: #1E1E1E;
                color: #ffffff;
                border: 1px solid #555555;
            }
            QListWidget::item {
                padding: 5px;
            }
        """)
        self.load_invoices()
        layout.addWidget(QLabel("Invoices:"))
        layout.addWidget(self.invoices_list)

        btn_layout = QHBoxLayout()
        self.view_invoice_button = QPushButton("View Invoice")
        self.view_invoice_button.clicked.connect(self.view_invoice)
        self.resend_invoice_button = QPushButton("Resend Invoice")
        self.resend_invoice_button.clicked.connect(self.resend_invoice)
        self.delete_invoice_button = QPushButton("Delete Invoice")
        self.delete_invoice_button.clicked.connect(self.delete_invoice)
        btn_layout.addWidget(self.view_invoice_button)
        btn_layout.addWidget(self.resend_invoice_button)
        btn_layout.addWidget(self.delete_invoice_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_invoices(self):
        self.invoices_list.clear()
        if not os.path.exists(INVOICES_DIR):
            return
        invoices = sorted(os.listdir(INVOICES_DIR), reverse=True)
        for invoice in invoices:
            item = QListWidgetItem(invoice)
            self.invoices_list.addItem(item)

    def get_selected_invoice_path(self):
        selected = self.invoices_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select an invoice.")
            return None
        invoice_filename = selected.text()
        invoice_path = os.path.join(INVOICES_DIR, invoice_filename)
        return invoice_path

    def view_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        if not os.path.exists(invoice_path):
            QMessageBox.warning(self, "File Not Found", "The selected invoice file does not exist.")
            return
        QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(os.path.abspath(invoice_path)))

    def resend_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        try:
            invoice_filename = os.path.basename(invoice_path)
            parts = invoice_filename.split('_invoice_')
            if len(parts) < 2:
                raise ValueError("Invalid invoice filename format.")
            invoice_number_part = parts[1].split('.')[0]
            invoice_number = str(int(invoice_number_part))
            invoice_metadata = self.parent.invoices_metadata.get(invoice_number, {})
            if not invoice_metadata:
                raise ValueError("Invoice metadata not found.")
            client_name = invoice_metadata["client_name"]
            if client_name not in self.parent.clients:
                raise ValueError("Client not found.")
            self.parent.invoice_tab.send_email_invoice(client_name, invoice_path)
        except Exception as e:
            QMessageBox.warning(self, "Resend Error", f"Failed to resend invoice: {e}")
            self.parent.log_message(f"Failed to resend invoice '{invoice_path}': {e}")

    def delete_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete invoice '{os.path.basename(invoice_path)}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            try:
                invoice_filename = os.path.basename(invoice_path)
                parts = invoice_filename.split('_invoice_')
                if len(parts) < 2:
                    raise ValueError("Invalid invoice filename format.")
                invoice_number_part = parts[1].split('.')[0]
                invoice_number = str(int(invoice_number_part))
                if invoice_number in self.parent.invoices_metadata:
                    del self.parent.invoices_metadata[invoice_number]
                    save_json(self.parent.invoices_metadata, INVOICES_METADATA_FILE)

                os.remove(invoice_path)
                self.parent.log_message(f"Invoice '{os.path.basename(invoice_path)}' deleted.")
                self.load_invoices()
                self.parent.financial_statements_tab.load_financial_statements()  # Refresh financial statements
            except Exception as e:
                QMessageBox.warning(self, "Delete Error", f"Failed to delete invoice: {e}")
                self.parent.log_message(f"Failed to delete invoice '{invoice_path}': {e}")

# Financial Statements Tab
class FinancialStatementsTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Client Selection
        client_layout = QHBoxLayout()
        self.client_combo = QComboBox(self)
        self.load_clients()
        client_layout.addWidget(QLabel("Select Client:"))
        client_layout.addWidget(self.client_combo)
        layout.addLayout(client_layout)

        # Financial Statements Table
        self.financial_table = QtWidgets.QTableWidget()
        self.financial_table.setColumnCount(5)
        self.financial_table.setHorizontalHeaderLabels(["Invoice Number", "Date", "Total (ZAR)", "Status", "Filename"])
        self.financial_table.horizontalHeader().setStretchLastSection(True)
        self.financial_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
        self.financial_table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
        layout.addWidget(self.financial_table)

        # Button to mark as paid
        btn_layout = QHBoxLayout()
        self.mark_paid_button = QPushButton("Mark as Paid")
        self.mark_paid_button.clicked.connect(self.mark_as_paid)
        btn_layout.addWidget(self.mark_paid_button)
        layout.addLayout(btn_layout)

        # Totals Display
        totals_layout = QHBoxLayout()
        self.total_invoiced_label = QLabel("Total Invoiced: R0.00")
        self.total_paid_label = QLabel("Total Paid: R0.00")  # Placeholder for paid amounts
        self.balance_label = QLabel("Balance: R0.00")
        totals_layout.addWidget(self.total_invoiced_label)
        totals_layout.addWidget(self.total_paid_label)
        totals_layout.addWidget(self.balance_label)
        layout.addLayout(totals_layout)

        # Load financial statements when client is selected
        self.client_combo.currentIndexChanged.connect(self.load_financial_statements)
        self.load_financial_statements()  # Initialize

        self.setLayout(layout)

    def load_clients(self):
        self.client_combo.clear()
        self.client_combo.addItems(list(self.parent.clients.keys()))

    def load_financial_statements(self):
        client_name = self.client_combo.currentText()
        if not client_name:
            return

        # Clear existing table
        self.financial_table.setRowCount(0)
        total_invoiced = 0.0
        total_paid = 0.0  # Placeholder for paid amounts
        balance = 0.0

        # Iterate through invoice metadata and populate table
        for invoice_number, metadata in self.parent.invoices_metadata.items():
            if metadata["client_name"] != client_name:
                continue
            invoice_number_display = metadata.get("invoice_number", "N/A")
            invoice_date = metadata.get("date", "N/A")
            total = metadata.get("total", 0.0)
            status = metadata.get("status", "Unpaid")
            filename = metadata.get("filename", "N/A")

            total_invoiced += total
            if status.lower() == "paid":
                total_paid += total
            balance += total if status.lower() != "paid" else 0.0

            # Add row to table
            row_position = self.financial_table.rowCount()
            self.financial_table.insertRow(row_position)
            self.financial_table.setItem(row_position, 0, QtWidgets.QTableWidgetItem(str(invoice_number_display)))
            self.financial_table.setItem(row_position, 1, QtWidgets.QTableWidgetItem(invoice_date))
            self.financial_table.setItem(row_position, 2, QtWidgets.QTableWidgetItem(f"R{total:.2f}"))
            self.financial_table.setItem(row_position, 3, QtWidgets.QTableWidgetItem(status))
            self.financial_table.setItem(row_position, 4, QtWidgets.QTableWidgetItem(filename))

        self.total_invoiced_label.setText(f"Total Invoiced: R{total_invoiced:.2f}")
        self.total_paid_label.setText(f"Total Paid: R{total_paid:.2f}")
        self.balance_label.setText(f"Balance: R{balance:.2f}")

    def mark_as_paid(self):
        selected_rows = self.financial_table.selectionModel().selectedRows()
        if not selected_rows:
            QMessageBox.warning(self, "Selection Error", "Please select an invoice to mark as paid.")
            return
        for selected in selected_rows:
            row = selected.row()
            invoice_number = self.financial_table.item(row, 0).text()
            current_status = self.financial_table.item(row, 3).text()
            if current_status.lower() == "paid":
                QMessageBox.information(self, "Already Paid", f"Invoice {invoice_number} is already marked as paid.")
                continue
            # Update status in metadata
            invoice_number_key = str(invoice_number)
            if invoice_number_key in self.parent.invoices_metadata:
                self.parent.invoices_metadata[invoice_number_key]["status"] = "Paid"
                save_json(self.parent.invoices_metadata, INVOICES_METADATA_FILE)
                self.financial_table.setItem(row, 3, QtWidgets.QTableWidgetItem("Paid"))
                self.parent.log_message(f"Invoice {invoice_number} marked as paid.")

        # Refresh financial statements
        self.load_financial_statements()

# Email Configuration Tab
class EmailConfigTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Email configuration form
        form_group = QtWidgets.QGroupBox("Email Configuration")
        form_layout = QVBoxLayout()
        self.sender_email_input = QLineEdit(self)
        self.sender_email_input.setPlaceholderText("Sender Email")
        self.sender_password_input = QLineEdit(self)
        self.sender_password_input.setPlaceholderText("Sender Password")
        self.sender_password_input.setEchoMode(QLineEdit.EchoMode.Password)
        self.smtp_server_input = QLineEdit(self)
        self.smtp_server_input.setPlaceholderText("SMTP Server (e.g., smtp.office365.com)")
        self.smtp_port_input = QLineEdit(self)
        self.smtp_port_input.setPlaceholderText("SMTP Port (e.g., 587)")

        form_layout.addWidget(QLabel("Sender Email:"))
        form_layout.addWidget(self.sender_email_input)
        form_layout.addWidget(QLabel("Sender Password:"))
        form_layout.addWidget(self.sender_password_input)
        form_layout.addWidget(QLabel("SMTP Server:"))
        form_layout.addWidget(self.smtp_server_input)
        form_layout.addWidget(QLabel("SMTP Port:"))
        form_layout.addWidget(self.smtp_port_input)

        form_group.setLayout(form_layout)
        layout.addWidget(form_group)

        # Save button
        self.save_email_config_button = QPushButton("Save Email Settings")
        self.save_email_config_button.clicked.connect(self.save_email_config)
        layout.addWidget(self.save_email_config_button)

        self.setLayout(layout)
        self.load_existing_config()

    def load_existing_config(self):
        config = self.parent.email_config
        if config:
            self.sender_email_input.setText(config.get("sender_email", ""))
            self.sender_password_input.setText(config.get("sender_password", ""))
            self.smtp_server_input.setText(config.get("smtp_server", ""))
            self.smtp_port_input.setText(str(config.get("smtp_port", "")))

    def save_email_config(self):
        sender_email = self.sender_email_input.text().strip()
        sender_password = self.sender_password_input.text().strip()
        smtp_server = self.smtp_server_input.text().strip()
        smtp_port = self.smtp_port_input.text().strip()

        if not sender_email or not sender_password or not smtp_server or not smtp_port:
            QMessageBox.warning(self, "Input Error", "Please fill in all fields.")
            return

        try:
            smtp_port = int(smtp_port)
        except ValueError:
            QMessageBox.warning(self, "Input Error", "SMTP Port must be a number.")
            return

        self.parent.email_config = {
            "sender_email": sender_email,
            "sender_password": sender_password,
            "smtp_server": smtp_server,
            "smtp_port": smtp_port
        }
        save_json(self.parent.email_config, EMAIL_CONFIG_FILE)
        self.parent.log_message("Email settings updated.")
        QMessageBox.information(self, "Success", "Email settings have been saved.")

# Company Details Tab
class CompanyDetailsTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Company details form
        form_group = QtWidgets.QGroupBox("Company Details")
        form_layout = QVBoxLayout()
        self.company_name_input = QLineEdit(self)
        self.company_name_input.setPlaceholderText("Company Name")
        self.company_address_input = QLineEdit(self)
        self.company_address_input.setPlaceholderText("Company Address")
        self.company_email_input = QLineEdit(self)
        self.company_email_input.setPlaceholderText("Company Email")
        self.company_phone_input = QLineEdit(self)
        self.company_phone_input.setPlaceholderText("Company Phone")
        self.company_logo_path_input = QLineEdit(self)
        self.company_logo_path_input.setPlaceholderText("Logo Path")
        self.browse_logo_button = QPushButton("Browse")
        self.browse_logo_button.clicked.connect(self.browse_logo)

        logo_layout = QHBoxLayout()
        logo_layout.addWidget(self.company_logo_path_input)
        logo_layout.addWidget(self.browse_logo_button)

        form_layout.addWidget(QLabel("Company Name:"))
        form_layout.addWidget(self.company_name_input)
        form_layout.addWidget(QLabel("Company Address:"))
        form_layout.addWidget(self.company_address_input)
        form_layout.addWidget(QLabel("Company Email:"))
        form_layout.addWidget(self.company_email_input)
        form_layout.addWidget(QLabel("Company Phone:"))
        form_layout.addWidget(self.company_phone_input)
        form_layout.addWidget(QLabel("Company Logo:"))
        form_layout.addLayout(logo_layout)

        form_group.setLayout(form_layout)
        layout.addWidget(form_group)

        # Save button
        self.save_company_details_button = QPushButton("Save Company Details")
        self.save_company_details_button.clicked.connect(self.save_company_details)
        layout.addWidget(self.save_company_details_button)

        self.setLayout(layout)
        self.load_existing_details()

    def browse_logo(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Select Logo", "", "Image Files (*.png *.jpg *.bmp)")
        if file_path:
            self.company_logo_path_input.setText(file_path)

    def load_existing_details(self):
        details = self.parent.company_details
        if details:
            self.company_name_input.setText(details.get("company_name", ""))
            self.company_address_input.setText(details.get("company_address", ""))
            self.company_email_input.setText(details.get("company_email", ""))
            self.company_phone_input.setText(details.get("company_phone", ""))
            self.company_logo_path_input.setText(details.get("company_logo_path", ""))

    def save_company_details(self):
        company_name = self.company_name_input.text().strip()
        company_address = self.company_address_input.text().strip()
        company_email = self.company_email_input.text().strip()
        company_phone = self.company_phone_input.text().strip()
        company_logo_path = self.company_logo_path_input.text().strip()

        if not company_name or not company_address or not company_email or not company_phone:
            QMessageBox.warning(self, "Input Error", "Please fill in all required fields.")
            return

        self.parent.company_details = {
            "company_name": company_name,
            "company_address": company_address,
            "company_email": company_email,
            "company_phone": company_phone,
            "company_logo_path": company_logo_path
        }
        save_json(self.parent.company_details, COMPANY_DETAILS_FILE)
        self.parent.log_message("Company details updated.")
        QMessageBox.information(self, "Success", "Company details have been saved.")

# Template Management Tab
class TemplateManagementTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # List of templates
        self.templates_list = QListWidget()
        self.load_templates()
        layout.addWidget(QLabel("Existing Templates:"))
        layout.addWidget(self.templates_list)

        # Buttons to create, edit, and delete templates
        btn_layout = QHBoxLayout()
        self.create_template_button = QPushButton("Create New Template")
        self.create_template_button.clicked.connect(self.create_template)
        self.edit_template_button = QPushButton("Edit Selected")
        self.edit_template_button.clicked.connect(self.edit_template)
        self.delete_template_button = QPushButton("Delete Selected")
        self.delete_template_button.clicked.connect(self.delete_template)
        btn_layout.addWidget(self.create_template_button)
        btn_layout.addWidget(self.edit_template_button)
        btn_layout.addWidget(self.delete_template_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_templates(self):
        self.templates_list.clear()
        for template in self.parent.templates.keys():
            item = QListWidgetItem(template)
            self.templates_list.addItem(item)

    def create_template(self):
        """Open CreateTemplate.py to create a new template."""
        try:
            # Assuming CreateTemplate.py is in the same directory and is executable
            os.system("python CreateTemplate.py")
            self.parent.log_message("Opened CreateTemplate.py")
            # Reload templates after creation
            self.load_templates()
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to open CreateTemplate.py: {e}")

    def edit_template(self):
        """Open EditTemplate.py to edit the selected template."""
        selected_item = self.templates_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, "Selection Error", "Please select a template to edit.")
            return
        template_name = selected_item.text()
        try:
            # Assuming EditTemplate.py can accept template name as argument
            os.system(f"python EditTemplate.py {template_name}")
            self.parent.log_message(f"Opened EditTemplate.py for template '{template_name}'")
            # Reload templates after editing
            self.load_templates()
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to open EditTemplate.py: {e}")

    def delete_template(self):
        """Logic to delete the selected template."""
        selected_item = self.templates_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, "Selection Error", "Please select a template to delete.")
            return
        template_name = selected_item.text()
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete template '{template_name}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            try:
                del self.parent.templates[template_name]
                save_json(self.parent.templates, TEMPLATES_FILE)
                self.parent.log_message(f"Template '{template_name}' deleted.")
                self.load_templates()
            except Exception as e:
                QMessageBox.warning(self, "Delete Error", f"Failed to delete template: {e}")
                self.parent.log_message(f"Failed to delete template '{template_name}': {e}")

# Dialog for editing a line item
class EditLineItemDialog(QDialog):
    def __init__(self, name, data, parent):
        super().__init__()
        self.setWindowTitle("Edit Line Item")
        self.parent = parent
        self.name = name
        self.data = data
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()

        self.name_input = QLineEdit(self)
        self.name_input.setText(self.name)
        self.price_input = QLineEdit(self)
        self.price_input.setText(str(self.data["price"]))

        self.layout.addWidget(QLabel("Name:"))
        self.layout.addWidget(self.name_input)
        self.layout.addWidget(QLabel("Price:"))
        self.layout.addWidget(self.price_input)

        self.button_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.layout.addWidget(self.button_box)

        self.setLayout(self.layout)

    def get_data(self):
        return (
            self.name_input.text().strip(),
            self.price_input.text().strip()
        )

# Invoice Creation Tab
class InvoiceCreationTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.line_items = []
        self.selected_template = None  # For template usage
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Template Selection
        template_layout = QHBoxLayout()
        self.template_combo = QComboBox(self)
        self.load_templates()
        self.template_combo.currentIndexChanged.connect(self.change_template)
        template_layout.addWidget(QLabel("Select Template:"))
        template_layout.addWidget(self.template_combo)
        layout.addLayout(template_layout)

        # Client selection
        client_layout = QHBoxLayout()
        self.client_combo = QComboBox(self)
        self.load_clients()
        client_layout.addWidget(QLabel("Select Client:"))
        client_layout.addWidget(self.client_combo)
        layout.addLayout(client_layout)

        # VAT Number Display (Read-only)
        vat_display_layout = QHBoxLayout()
        self.vat_display = QLineEdit(self)
        self.vat_display.setReadOnly(True)
        vat_display_layout.addWidget(QLabel("Client VAT Number:"))
        vat_display_layout.addWidget(self.vat_display)
        layout.addLayout(vat_display_layout)

        # Update VAT display when client changes
        self.client_combo.currentIndexChanged.connect(self.update_vat_display)
        self.update_vat_display()  # Initialize VAT display

        # Line Items selection
        line_item_layout = QHBoxLayout()
        self.item_combo = QComboBox(self)
        self.load_line_items()
        self.quantity_input = QSpinBox(self)
        self.quantity_input.setMinimum(1)
        self.quantity_input.setValue(1)
        self.add_item_button = QPushButton("Add Item")
        self.add_item_button.clicked.connect(self.add_line_item)
        line_item_layout.addWidget(QLabel("Item:"))
        line_item_layout.addWidget(self.item_combo)
        line_item_layout.addWidget(QLabel("Quantity:"))
        line_item_layout.addWidget(self.quantity_input)
        line_item_layout.addWidget(self.add_item_button)
        layout.addLayout(line_item_layout)

        # List to display added line items
        self.added_items_list = QListWidget()
        self.added_items_list.setStyleSheet("""
            QListWidget {
                background-color: #1E1E1E;
                color: #ffffff;
                border: 1px solid #555555;
            }
            QListWidget::item {
                padding: 5px;
            }
        """)
        layout.addWidget(QLabel("Added Line Items:"))
        layout.addWidget(self.added_items_list)

        # Discounts and Taxes
        discounts_taxes_layout = QHBoxLayout()
        self.discount_input = QLineEdit(self)
        self.discount_input.setPlaceholderText("Discount (%)")
        self.tax_rate_input = QLineEdit(self)
        self.tax_rate_input.setPlaceholderText("Tax Rate (%)")
        discounts_taxes_layout.addWidget(QLabel("Discount (%):"))
        discounts_taxes_layout.addWidget(self.discount_input)
        discounts_taxes_layout.addWidget(QLabel("Tax Rate (%):"))
        discounts_taxes_layout.addWidget(self.tax_rate_input)
        layout.addLayout(discounts_taxes_layout)

        # Output Format Selection
        output_layout = QHBoxLayout()
        self.output_format_combo = QComboBox(self)
        self.output_format_combo.addItems(["PDF", "Excel"])
        output_layout.addWidget(QLabel("Output Format:"))
        output_layout.addWidget(self.output_format_combo)
        layout.addLayout(output_layout)

        # Button to create invoice
        self.create_invoice_button = QPushButton("Create Invoice")
        self.create_invoice_button.clicked.connect(self.create_invoice)
        layout.addWidget(self.create_invoice_button)

        self.setLayout(layout)

    def load_templates(self):
        self.template_combo.clear()
        templates_list = list(self.parent.templates.keys())
        self.template_combo.addItems(["Default"] + templates_list)
        self.selected_template = "Default"

    def change_template(self, index):
        selected = self.template_combo.currentText()
        if selected == "Default":
            self.selected_template = None
        else:
            self.selected_template = selected

    def load_clients(self):
        self.client_combo.clear()
        self.client_combo.addItems(list(self.parent.clients.keys()))

    def load_line_items(self):
        self.item_combo.clear()
        self.item_combo.addItems(list(self.parent.line_items_data.keys()))

    def add_line_item(self):
        item_name = self.item_combo.currentText()
        quantity = self.quantity_input.value()
        if not item_name:
            QMessageBox.warning(self, "Selection Error", "Please select a line item.")
            return
        price = self.parent.line_items_data[item_name]['price']
        total = price * quantity
        self.line_items.append({
            "item": item_name,
            "quantity": quantity,
            "price": price,
            "total": total
        })
        self.update_added_items_list()

    def update_added_items_list(self):
        self.added_items_list.clear()
        for idx, item in enumerate(self.line_items, 1):
            self.added_items_list.addItem(
                f"{idx}. {item['item']} - Qty: {item['quantity']} - Price: R{item['price']:.2f} - Total: R{item['total']:.2f}"
            )

    def update_vat_display(self):
        client_name = self.client_combo.currentText()
        vat_number = self.parent.clients.get(client_name, {}).get("vat_number", "")
        self.vat_display.setText(vat_number)

    def create_invoice(self):
        client_name = self.client_combo.currentText()
        vat_number = self.parent.clients.get(client_name, {}).get("vat_number", "")
        discount = self.discount_input.text().strip()
        tax_rate = self.tax_rate_input.text().strip()

        if not client_name:
            QMessageBox.warning(self, "Input Error", "Please select a client.")
            return

        if not self.line_items:
            QMessageBox.warning(self, "Input Error", "Please add at least one line item.")
            return

        # Calculate totals
        subtotal = sum(item['total'] for item in self.line_items)
        discount_amount = 0
        if discount:
            try:
                discount_percentage = float(discount)
                discount_amount = subtotal * (discount_percentage / 100)
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid discount percentage.")
                return

        subtotal_after_discount = subtotal - discount_amount

        tax_amount = 0
        if tax_rate:
            try:
                tax_percentage = float(tax_rate)
                tax_amount = subtotal_after_discount * (tax_percentage / 100)
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid tax rate percentage.")
                return

        total = subtotal_after_discount + tax_amount

        invoice_date = datetime.now().strftime("%Y-%m-%d")  # Automatic date filling
        client_address = self.parent.clients[client_name]["address"]
        invoice_filename = f"{client_name.replace(' ', '_')}_invoice_{self.parent.invoice_counter}"

        # Prepare DataFrame for Excel/PDF
        df = pd.DataFrame([
            {"Item": item['item'], "Quantity": item['quantity'], "Price (ZAR)": item['price'], "Total (ZAR)": item['total']}
            for item in self.line_items
        ])

        output_format = self.output_format_combo.currentText()
        success = False
        if output_format == "PDF":
            success = self.create_pdf(client_name, vat_number, invoice_date, client_address, df, invoice_filename, subtotal, discount_amount, tax_amount, total)
        elif output_format == "Excel":
            success = self.create_excel(client_name, vat_number, invoice_date, client_address, df, invoice_filename, subtotal, discount_amount, tax_amount, total)
        else:
            QMessageBox.warning(self, "Format Error", "Please select a valid output format.")
            return

        if success:
            # Log invoice metadata
            invoice_metadata = {
                "invoice_number": self.parent.invoice_counter - 1,
                "client_name": client_name,
                "date": invoice_date,
                "total": total,
                "status": "Unpaid",
                "filename": f"{invoice_filename}.{output_format.lower()}"
            }
            self.parent.invoices_metadata[str(self.parent.invoice_counter - 1)] = invoice_metadata
            save_json(self.parent.invoices_metadata, INVOICES_METADATA_FILE)

            # Reset form
            self.line_items = []
            self.update_added_items_list()
            self.parent.invoice_counter += 1  # Increment invoice counter after successful creation
            self.parent.update_invoice_counter()

    def apply_template_attributes(self, c, template, invoice_details):
        """Applies template attributes like header, footer, and logo to the PDF."""
        if 'header_text' in template:
            c.setFont("Helvetica-Bold", 16)
            c.drawString(50, invoice_details['height'] - 50, template['header_text'])

        if 'footer_text' in template:
            c.setFont("Helvetica", 10)
            c.drawString(50, 30, template['footer_text'])

        if 'logo_path' in template and template['logo_path']:
            if os.path.exists(template['logo_path']):
                c.drawImage(template['logo_path'], invoice_details['width'] - 150, invoice_details['height'] - 100, width=100, height=50)

    def create_pdf(self, client, vat, invoice_date, client_address, df, invoice_filename, subtotal, discount, tax, total):
        """Generate PDF invoice."""
        pdf_filename = os.path.join(INVOICES_DIR, f"{invoice_filename}.pdf")
        template = self.get_selected_template()

        c = canvas.Canvas(pdf_filename, pagesize=letter)
        width, height = letter

        # Apply Template Attributes
        invoice_details = {'width': width, 'height': height}
        self.apply_template_attributes(c, template, invoice_details)

        # Company Details
        company_name = self.parent.company_details.get("company_name", "Your Company Name")
        company_address = self.parent.company_details.get("company_address", "Your

turns-00049.parquet:14134

a49d8fee7e81382bba3d07e7
turn 8/8gpt-4o-mini-2024-07-18EnglishSouth Africa4312 words
degenerate_repetitionAbsentFinal dense release
USER
carry on
ASSISTANT
Certainly! Here is the continuation and completion of the previously shared code snippet for your invoicing application:

```python
        company_email = self.parent.company_details.get("company_email", "your.email@example.com")
        company_phone = self.parent.company_details.get("company_phone", "123-456-7890")

        # Optional: Company logo
        logo_path = self.parent.company_details.get("company_logo_path", "")
        if logo_path and os.path.exists(logo_path):
            c.drawImage(logo_path, 50, height - 100, width=100, height=50)

        c.setFont("Helvetica-Bold", 14)
        c.drawString(50, height - 120, company_name)
        c.setFont("Helvetica", 12)
        c.drawString(50, height - 140, company_address)
        c.drawString(50, height - 160, f"Email: {company_email}")
        c.drawString(50, height - 180, f"Phone: {company_phone}")

        # Invoice Header
        c.setFont("Helvetica-Bold", 20)
        c.drawString(400, height - 120, "INVOICE")  # Adjusted position for "INVOICE"

        # Invoice Information
        c.setFont("Helvetica", 12)
        c.drawString(400, height - 150, f"Invoice Number: {self.parent.invoice_counter - 1}")  # Displaying counter
        c.drawString(400, height - 170, f"Date: {invoice_date}")

        # Client Information
        c.setFont("Helvetica-Bold", 12)
        c.drawString(50, height - 220, "Bill To:")
        c.setFont("Helvetica", 12)
        c.drawString(50, height - 240, f"{client}")
        c.drawString(50, height - 260, f"Address: {client_address}")
        if vat:
            c.drawString(50, height - 280, f"VAT Number: {vat}")

        # Draw a box around client information
        c.rect(40, height - 290, 500, 60, stroke=1, fill=0)

        # Table Headers
        c.setFont("Helvetica-Bold", 12)
        y = height - 350
        c.drawString(50, y, "Item")
        c.drawString(250, y, "Quantity")
        c.drawString(350, y, "Price (ZAR)")
        c.drawString(450, y, "Total (ZAR)")
        y -= 20
        c.line(50, y, 550, y)

        # Table Content
        c.setFont("Helvetica", 12)
        for index, row in df.iterrows():
            y -= 20
            if y < 150:
                c.showPage()
                y = height - 50
                # Re-apply template on new page
                self.apply_template_attributes(c, template, {'width': width, 'height': height})
                # Re-draw company details
                c.setFont("Helvetica-Bold", 14)
                c.drawString(50, height - 120, company_name)
                c.setFont("Helvetica", 12)
                c.drawString(50, height - 140, company_address)
                c.drawString(50, height - 160, f"Email: {company_email}")
                c.drawString(50, height - 180, f"Phone: {company_phone}")
                # Re-draw invoice header
                c.setFont("Helvetica-Bold", 20)
                c.drawString(400, height - 120, "INVOICE")
                c.setFont("Helvetica", 12)
                c.drawString(400, height - 150, f"Invoice Number: {self.parent.invoice_counter - 1}")
                c.drawString(400, height - 170, f"Date: {invoice_date}")
                # Re-draw client information
                c.setFont("Helvetica-Bold", 12)
                c.drawString(50, height - 220, "Bill To:")
                c.setFont("Helvetica", 12)
                c.drawString(50, height - 240, f"{client}")
                c.drawString(50, height - 260, f"Address: {client_address}")
                if vat:
                    c.drawString(50, height - 280, f"VAT Number: {vat}")

                # Re-draw table headers
                c.setFont("Helvetica-Bold", 12)
                y = height - 300
                c.drawString(50, y, "Item")
                c.drawString(250, y, "Quantity")
                c.drawString(350, y, "Price (ZAR)")
                c.drawString(450, y, "Total (ZAR)")
                y -= 20
                c.line(50, y, 550, y)

            c.drawString(50, y, str(row['Item']))
            c.drawString(250, y, str(row['Quantity']))
            c.drawString(350, y, f"R{row['Price (ZAR)']:.2f}")
            c.drawString(450, y, f"R{row['Total (ZAR)']:.2f}")

            # Draw lines around each row
            c.line(45, y - 5, 555, y - 5)

        # Totals
        y -= 40
        c.setFont("Helvetica-Bold", 12)
        c.drawString(350, y, "Subtotal:")
        c.drawString(450, y, f"R{subtotal:.2f}")
        y -= 20
        if discount > 0:
            c.drawString(350, y, "Discount:")
            c.drawString(450, y, f"R{-discount:.2f}")
            y -= 20
        if tax > 0:
            c.drawString(350, y, "Tax:")
            c.drawString(450, y, f"R{tax:.2f}")
            y -= 20
        c.drawString(350, y, "Total:")
        c.drawString(450, y, f"R{total:.2f}")

        # Draw a box around the totals
        c.rect(340, y - 10, 220, 60, stroke=1, fill=0)

        # Footer
        c.setFont("Helvetica-Oblique", 10)
        c.drawString(50, 30, "Thank you for your business.")  # Added footnote

        c.save()
        self.parent.log_message(f"Invoice saved as {pdf_filename}")
        self.parent.manage_invoices_tab.load_invoices()
        self.parent.financial_statements_tab.load_financial_statements()  # Refresh financial statements
        self.send_email_invoice(client, pdf_filename)

    def create_excel(self, client, vat, invoice_date, client_address, df, invoice_filename, subtotal, discount, tax, total):
        """Generate Excel invoice."""
        try:
            excel_filename = os.path.join(INVOICES_DIR, f"{invoice_filename}.xlsx")
            template = self.get_selected_template()

            writer = pd.ExcelWriter(excel_filename, engine='openpyxl')
            df.to_excel(writer, index=False, sheet_name='Invoice')

            # Access the workbook and worksheet
            workbook = writer.book
            worksheet = writer.sheets['Invoice']

            # Apply Template Attributes (basic - headers)
            current_row = 1
            if 'header_text' in template:
                worksheet.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=4)
                cell = worksheet.cell(row=current_row, column=1)
                cell.value = template['header_text']
                cell.font = cell.font.copy(bold=True, size=16)
                current_row += 1

            # Company Details
            company_name = self.parent.company_details.get("company_name", "Your Company Name")
            company_address = self.parent.company_details.get("company_address", "Your Company Address")
            company_email = self.parent.company_details.get("company_email", "your.email@example.com")
            company_phone = self.parent.company_details.get("company_phone", "123-456-7890")
            company_logo = self.parent.company_details.get("company_logo_path", "")

            if company_logo and os.path.exists(company_logo):
                # Insert company logo if available
                img = QtGui.QPixmap(company_logo)
                img.save('temp_logo.png')  # Save temporarily
                from openpyxl.drawing.image import Image as XLImage
                img_excel = XLImage('temp_logo.png')
                img_excel.anchor = 'A1'
                worksheet.add_image(img_excel, 'A1')
                current_row += 2  # Adjust row if logo is added

            worksheet.cell(row=current_row, column=1, value=company_name)
            worksheet.cell(row=current_row, column=2, value=f"Invoice Number: {self.parent.invoice_counter - 1}")
            current_row += 1
            worksheet.cell(row=current_row, column=1, value=company_address)
            worksheet.cell(row=current_row, column=2, value=f"Date: {invoice_date}")
            current_row += 1
            worksheet.cell(row=current_row, column=1, value=f"Email: {company_email}")
            worksheet.cell(row=current_row, column=2, value=f"Client: {client}")
            current_row += 1
            worksheet.cell(row=current_row, column=1, value=f"Phone: {company_phone}")
            worksheet.cell(row=current_row, column=2, value=f"Address: {client_address}")
            current_row += 1
            if vat:
                worksheet.cell(row=current_row, column=1, value=f"VAT Number: {vat}")
                current_row += 1

            # Add totals
            start_row = len(df) + current_row + 2
            worksheet.cell(row=start_row, column=3, value="Subtotal:")
            worksheet.cell(row=start_row, column=4, value=subtotal)
            if discount > 0:
                worksheet.cell(row=start_row + 1, column=3, value="Discount:")
                worksheet.cell(row=start_row + 1, column=4, value=-discount)
            if tax > 0:
                worksheet.cell(row=start_row + 2, column=3, value="Tax:")
                worksheet.cell(row=start_row + 2, column=4, value=tax)
            worksheet.cell(row=start_row + 3, column=3, value="Total:")
            worksheet.cell(row=start_row + 3, column=4, value=total)

            # Add footnote
            worksheet.cell(row=start_row + 5, column=1, value="Thank you for your business.")

            # Apply Template Styles (basic)
            for row in worksheet.iter_rows(min_row=1, max_row=start_row + 5, min_col=1, max_col=5):
                for cell in row:
                    cell.font = cell.font.copy(color="FFFFFF")  # White text
                    cell.fill = cell.fill.copy(start_color="1E1E1E")  # Dark background

            writer.save()
            os.remove('temp_logo.png')  # Clean up temporary logo
            self.parent.log_message(f"Invoice saved as {excel_filename}")
            self.parent.manage_invoices_tab.load_invoices()
            self.parent.financial_statements_tab.load_financial_statements()  # Refresh financial statements
            self.send_email_invoice(client, excel_filename)
            return True
        except Exception as e:
            QMessageBox.warning(self, "Excel Generation Error", f"Failed to generate Excel invoice: {e}")
            self.parent.log_message(f"Failed to generate Excel invoice '{excel_filename}': {e}")
            return False

# Manage Invoices Tab
class ManageInvoicesTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        self.invoices_list = QListWidget()
        self.invoices_list.setStyleSheet("""
            QListWidget {
                background-color: #1E1E1E;
                color: #ffffff;
                border: 1px solid #555555;
            }
            QListWidget::item {
                padding: 5px;
            }
        """)
        self.load_invoices()
        layout.addWidget(QLabel("Invoices:"))
        layout.addWidget(self.invoices_list)

        btn_layout = QHBoxLayout()
        self.view_invoice_button = QPushButton("View Invoice")
        self.view_invoice_button.clicked.connect(self.view_invoice)
        self.resend_invoice_button = QPushButton("Resend Invoice")
        self.resend_invoice_button.clicked.connect(self.resend_invoice)
        self.delete_invoice_button = QPushButton("Delete Invoice")
        self.delete_invoice_button.clicked.connect(self.delete_invoice)
        btn_layout.addWidget(self.view_invoice_button)
        btn_layout.addWidget(self.resend_invoice_button)
        btn_layout.addWidget(self.delete_invoice_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_invoices(self):
        self.invoices_list.clear()
        if not os.path.exists(INVOICES_DIR):
            return
        invoices = sorted(os.listdir(INVOICES_DIR), reverse=True)
        for invoice in invoices:
            item = QListWidgetItem(invoice)
            self.invoices_list.addItem(item)

    def get_selected_invoice_path(self):
        selected = self.invoices_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select an invoice.")
            return None
        invoice_filename = selected.text()
        invoice_path = os.path.join(INVOICES_DIR, invoice_filename)
        return invoice_path

    def view_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        if not os.path.exists(invoice_path):
            QMessageBox.warning(self, "File Not Found", "The selected invoice file does not exist.")
            return
        QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(os.path.abspath(invoice_path)))

    def resend_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        try:
            invoice_filename = os.path.basename(invoice_path)
            parts = invoice_filename.split('_invoice_')
            if len(parts) < 2:
                raise ValueError("Invalid invoice filename format.")
            invoice_number_part = parts[1].split('.')[0]
            invoice_number = str(int(invoice_number_part))
            invoice_metadata = self.parent.invoices_metadata.get(invoice_number, {})
            if not invoice_metadata:
                raise ValueError("Invoice metadata not found.")
            client_name = invoice_metadata["client_name"]
            if client_name not in self.parent.clients:
                raise ValueError("Client not found.")
            self.parent.invoice_tab.send_email_invoice(client_name, invoice_path)
        except Exception as e:
            QMessageBox.warning(self, "Resend Error", f"Failed to resend invoice: {e}")
            self.parent.log_message(f"Failed to resend invoice '{invoice_path}': {e}")

    def delete_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete invoice '{os.path.basename(invoice_path)}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            try:
                invoice_filename = os.path.basename(invoice_path)
                parts = invoice_filename.split('_invoice_')
                if len(parts) < 2:
                    raise ValueError("Invalid invoice filename format.")
                invoice_number_part = parts[1].split('.')[0]
                invoice_number = str(int(invoice_number_part))
                if invoice_number in self.parent.invoices_metadata:
                    del self.parent.invoices_metadata[invoice_number]
                    save_json(self.parent.invoices_metadata, INVOICES_METADATA_FILE)

                os.remove(invoice_path)
                self.parent.log_message(f"Invoice '{os.path.basename(invoice_path)}' deleted.")
                self.load_invoices()
                self.parent.financial_statements_tab.load_financial_statements()  # Refresh financial statements
            except Exception as e:
                QMessageBox.warning(self, "Delete Error", f"Failed to delete invoice: {e}")
                self.parent.log_message(f"Failed to delete invoice '{invoice_path}': {e}")

# Financial Statements Tab
class FinancialStatementsTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Client Selection
        client_layout = QHBoxLayout()
        self.client_combo = QComboBox(self)
        self.load_clients()
        client_layout.addWidget(QLabel("Select Client:"))
        client_layout.addWidget(self.client_combo)
        layout.addLayout(client_layout)

        # Financial Statements Table
        self.financial_table = QtWidgets.QTableWidget()
        self.financial_table.setColumnCount(5)
        self.financial_table.setHorizontalHeaderLabels(["Invoice Number", "Date", "Total (ZAR)", "Status", "Filename"])
        self.financial_table.horizontalHeader().setStretchLastSection(True)
        self.financial_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
        self.financial_table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
        layout.addWidget(self.financial_table)

        # Button to mark as paid
        btn_layout = QHBoxLayout()
        self.mark_paid_button = QPushButton("Mark as Paid")
        self.mark_paid_button.clicked.connect(self.mark_as_paid)
        btn_layout.addWidget(self.mark_paid_button)
        layout.addLayout(btn_layout)

        # Totals Display
        totals_layout = QHBoxLayout()
        self.total_invoiced_label = QLabel("Total Invoiced: R0.00")
        self.total_paid_label = QLabel("Total Paid: R0.00")  
        self.balance_label = QLabel("Balance: R0.00")
        totals_layout.addWidget(self.total_invoiced_label)
        totals_layout.addWidget(self.total_paid_label)
        totals_layout.addWidget(self.balance_label)
        layout.addLayout(totals_layout)

        # Load financial statements when client is selected
        self.client_combo.currentIndexChanged.connect(self.load_financial_statements)
        self.load_financial_statements()  # Initialize

        self.setLayout(layout)

    def load_clients(self):
        self.client_combo.clear()
        self.client_combo.addItems(list(self.parent.clients.keys()))

    def load_financial_statements(self):
        client_name = self.client_combo.currentText()
        if not client_name:
            return

        # Clear existing table
        self.financial_table.setRowCount(0)
        total_invoiced = 0.0
        total_paid = 0.0  
        balance = 0.0

        # Populate table with invoice data
        for invoice_number, metadata in self.parent.invoices_metadata.items():
            if metadata["client_name"] != client_name:
                continue
            invoice_number_display = metadata.get("invoice_number", "N/A")
            invoice_date = metadata.get("date", "N/A")
            total = metadata.get("total", 0.0)
            status = metadata.get("status", "Unpaid")
            filename = metadata.get("filename", "N/A")

            total_invoiced += total
            if status.lower() == "paid":
                total_paid += total
            balance += total if status.lower() != "paid" else 0.0

            # Add row to table
            row_position = self.financial_table.rowCount()
            self.financial_table.insertRow(row_position)
            self.financial_table.setItem(row_position, 0, QtWidgets.QTableWidgetItem(str(invoice_number_display)))
            self.financial_table.setItem(row_position, 1, QtWidgets.QTableWidgetItem(invoice_date))
            self.financial_table.setItem(row_position, 2, QtWidgets.QTableWidgetItem(f"R{total:.2f}"))
            self.financial_table.setItem(row_position, 3, QtWidgets.QTableWidgetItem(status))
            self.financial_table.setItem(row_position, 4, QtWidgets.QTableWidgetItem(filename))

        self.total_invoiced_label.setText(f"Total Invoiced: R{total_invoiced:.2f}")
        self.total_paid_label.setText(f"Total Paid: R{total_paid:.2f}")
        self.balance_label.setText(f"Balance: R{balance:.2f}")

    def mark_as_paid(self):
        selected_rows = self.financial_table.selectionModel().selectedRows()
        if not selected_rows:
            QMessageBox.warning(self, "Selection Error", "Please select an invoice to mark as paid.")
            return
        for selected in selected_rows:
            row = selected.row()
            invoice_number = self.financial_table.item(row, 0).text()
            current_status = self.financial_table.item(row, 3).text()
            if current_status.lower() == "paid":
                QMessageBox.information(self, "Already Paid", f"Invoice {invoice_number} is already marked as paid.")
                continue
            # Update status in metadata
            invoice_number_key = str(invoice_number)
            if invoice_number_key in self.parent.invoices_metadata:
                self.parent.invoices_metadata[invoice_number_key]["status"] = "Paid"
                save_json(self.parent.invoices_metadata, INVOICES_METADATA_FILE)
                self.financial_table.setItem(row, 3, QtWidgets.QTableWidgetItem("Paid"))
                self.parent.log_message(f"Invoice {invoice_number} marked as paid.")

        # Refresh financial statements
        self.load_financial_statements()

# Email Configuration Tab
class EmailConfigTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Email configuration form
        form_group = QtWidgets.QGroupBox("Email Configuration")
        form_layout = QVBoxLayout()
        self.sender_email_input = QLineEdit(self)
        self.sender_email_input.setPlaceholderText("Sender Email")
        self.sender_password_input = QLineEdit(self)
        self.sender_password_input.setPlaceholderText("Sender Password")
        self.sender_password_input.setEchoMode(QLineEdit.EchoMode.Password)
        self.smtp_server_input = QLineEdit(self)
        self.smtp_server_input.setPlaceholderText("SMTP Server (e.g., smtp.office365.com)")
        self.smtp_port_input = QLineEdit(self)
        self.smtp_port_input.setPlaceholderText("SMTP Port (e.g., 587)")

        form_layout.addWidget(QLabel("Sender Email:"))
        form_layout.addWidget(self.sender_email_input)
        form_layout.addWidget(QLabel("Sender Password:"))
        form_layout.addWidget(self.sender_password_input)
        form_layout.addWidget(QLabel("SMTP Server:"))
        form_layout.addWidget(self.smtp_server_input)
        form_layout.addWidget(QLabel("SMTP Port:"))
        form_layout.addWidget(self.smtp_port_input)

        form_group.setLayout(form_layout)
        layout.addWidget(form_group)

        # Save button
        self.save_email_config_button = QPushButton("Save Email Settings")
        self.save_email_config_button.clicked.connect(self.save_email_config)
        layout.addWidget(self.save_email_config_button)

        self.setLayout(layout)
        self.load_existing_config()

    def load_existing_config(self):
        config = self.parent.email_config
        if config:
            self.sender_email_input.setText(config.get("sender_email", ""))
            self.sender_password_input.setText(config.get("sender_password", ""))
            self.smtp_server_input.setText(config.get("smtp_server", ""))
            self.smtp_port_input.setText(str(config.get("smtp_port", "")))

    def save_email_config(self):
        sender_email = self.sender_email_input.text().strip()
        sender_password = self.sender_password_input.text().strip()
        smtp_server = self.smtp_server_input.text().strip()
        smtp_port = self.smtp_port_input.text().strip()

        if not sender_email or not sender_password or not smtp_server or not smtp_port:
            QMessageBox.warning(self, "Input Error", "Please fill in all fields.")
            return

        try:
            smtp_port = int(smtp_port)
        except ValueError:
            QMessageBox.warning(self, "Input Error", "SMTP Port must be a number.")
            return

        self.parent.email_config = {
            "sender_email": sender_email,
            "sender_password": sender_password,
            "smtp_server": smtp_server,
            "smtp_port": smtp_port
        }
        save_json(self.parent.email_config, EMAIL_CONFIG_FILE)
        self.parent.log_message("Email settings updated.")
        QMessageBox.information(self, "Success", "Email settings have been saved.")

# Company Details Tab
class CompanyDetailsTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Company details form
        form_group = QtWidgets.QGroupBox("Company Details")
        form_layout = QVBoxLayout()
        self.company_name_input = QLineEdit(self)
        self.company_name_input.setPlaceholderText("Company Name")
        self.company_address_input = QLineEdit(self)
        self.company_address_input.setPlaceholderText("Company Address")
        self.company_email_input = QLineEdit(self)
        self.company_email_input.setPlaceholderText("Company Email")
        self.company_phone_input = QLineEdit(self)
        self.company_phone_input.setPlaceholderText("Company Phone")
        self.company_logo_path_input = QLineEdit(self)
        self.company_logo_path_input.setPlaceholderText("Logo Path")
        self.browse_logo_button = QPushButton("Browse")
        self.browse_logo_button.clicked.connect(self.browse_logo)

        logo_layout = QHBoxLayout()
        logo_layout.addWidget(self.company_logo_path_input)
        logo_layout.addWidget(self.browse_logo_button)

        form_layout.addWidget(QLabel("Company Name:"))
        form_layout.addWidget(self.company_name_input)
        form_layout.addWidget(QLabel("Company Address:"))
        form_layout.addWidget(self.company_address_input)
        form_layout.addWidget(QLabel("Company Email:"))
        form_layout.addWidget(self.company_email_input)
        form_layout.addWidget(QLabel("Company Phone:"))
        form_layout.addWidget(self.company_phone_input)
        form_layout.addWidget(QLabel("Company Logo:"))
        form_layout.addLayout(logo_layout)

        form_group.setLayout(form_layout)
        layout.addWidget(form_group)

        # Save button
        self.save_company_details_button = QPushButton("Save Company Details")
        self.save_company_details_button.clicked.connect(self.save_company_details)
        layout.addWidget(self.save_company_details_button)

        self.setLayout(layout)
        self.load_existing_details()

    def browse_logo(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Select Logo", "", "Image Files (*.png *.jpg *.bmp)")
        if file_path:
            self.company_logo_path_input.setText(file_path)

    def load_existing_details(self):
        details = self.parent.company_details
        if details:
            self.company_name_input.setText(details.get("company_name", ""))
            self.company_address_input.setText(details.get("company_address", ""))
            self.company_email_input.setText(details.get("company_email", ""))
            self.company_phone_input.setText(details.get("company_phone", ""))
            self.company_logo_path_input.setText(details.get("company_logo_path", ""))

    def save_company_details(self):
        company_name = self.company_name_input.text().strip()
        company_address = self.company_address_input.text().strip()
        company_email = self.company_email_input.text().strip()
        company_phone = self.company_phone_input.text().strip()
        company_logo_path = self.company_logo_path_input.text().strip()

        if not company_name or not company_address or not company_email or not company_phone:
            QMessageBox.warning(self, "Input Error", "Please fill in all required fields.")
            return

        self.parent.company_details = {
            "company_name": company_name,
            "company_address": company_address,
            "company_email": company_email,
            "company_phone": company_phone,
            "company_logo_path": company_logo_path
        }
        save_json(self.parent.company_details, COMPANY_DETAILS_FILE)
        self.parent.log_message("Company details updated.")
        QMessageBox.information(self, "Success", "Company details have been saved.")

# Template Management Tab
class TemplateManagementTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # List of templates
        self.templates_list = QListWidget()
        self.load_templates()
        layout.addWidget(QLabel("Existing Templates:"))
        layout.addWidget(self.templates_list)

        # Buttons to create, edit, and delete templates
        btn_layout = QHBoxLayout()
        self.create_template_button = QPushButton("Create New Template")
        self.create_template_button.clicked.connect(self.create_template)
        self.edit_template_button = QPushButton("Edit Selected")
        self.edit_template_button.clicked.connect(self.edit_template)
        self.delete_template_button = QPushButton("Delete Selected")
        self.delete_template_button.clicked.connect(self.delete_template)
        btn_layout.addWidget(self.create_template_button)
        btn_layout.addWidget(self.edit_template_button)
        btn_layout.addWidget(self.delete_template_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_templates(self):
        self.templates_list.clear()
        for template in self.parent.templates.keys():
            item = QListWidgetItem(template)
            self.templates_list.addItem(item)

    def create_template(self):
        """Open CreateTemplate.py to create a new template."""
        try:
            # Assuming CreateTemplate.py is in the same directory and is executable
            os.system("python CreateTemplate.py")
            self.parent.log_message("Opened CreateTemplate.py")
            # Reload templates after creation
            self.load_templates()
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to open CreateTemplate.py: {e}")

    def edit_template(self):
        """Open EditTemplate.py to edit the selected template."""
        selected_item = self.templates_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, "Selection Error", "Please select a template to edit.")
            return
        template_name = selected_item.text()
        try:
            # Assuming EditTemplate.py can accept template name as argument
            os.system(f"python EditTemplate.py {template_name}")
            self.parent.log_message(f"Opened EditTemplate.py for template '{template_name}'")
            # Reload templates after editing
            self.load_templates()
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Failed to open EditTemplate.py: {e}")

    def delete_template(self):
        """Logic to delete the selected template."""
        selected_item = self.templates_list.currentItem()
        if not selected_item:
            QMessageBox.warning(self, "Selection Error", "Please select a template to delete.")
            return
        template_name = selected_item.text()
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete template '{template_name}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            try:
                del self.parent.templates[template_name]
                save_json(self.parent.templates, TEMPLATES_FILE)
                self.parent.log_message(f"Template '{template_name}' deleted.")
                self.load_templates()
            except Exception as e:
                QMessageBox.warning(self, "Delete Error", f"Failed to delete template: {e}")
                self.parent.log_message(f"Failed to delete template '{template_name}': {e}")

# Dialog for editing a line item
class EditLineItemDialog(QDialog):
    def __init__(self, name, data, parent):
        super().__init__()
        self.setWindowTitle("Edit Line Item")
        self.parent = parent
        self.name = name
        self.data = data
        self.init_ui()

    def init_ui(self):
        self.layout = QVBoxLayout()

        self.name_input = QLineEdit(self)
        self.name_input.setText(self.name)
        self.price_input = QLineEdit(self)
        self.price_input.setText(str(self.data["price"]))

        self.layout.addWidget(QLabel("Name:"))
        self.layout.addWidget(self.name_input)
        self.layout.addWidget(QLabel("Price:"))
        self.layout.addWidget(self.price_input)

        self.button_box = QtWidgets.QDialogButtonBox(
            QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)
        self.layout.addWidget(self.button_box)

        self.setLayout(self.layout)

    def get_data(self):
        return (
            self.name_input.text().strip(),
            self.price_input.text().strip()
        )

# Invoice Creation Tab
class InvoiceCreationTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.line_items = []
        self.selected_template = None  # For template usage
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Template Selection
        template_layout = QHBoxLayout()
        self.template_combo = QComboBox(self)
        self.load_templates()
        self.template_combo.currentIndexChanged.connect(self.change_template)
        template_layout.addWidget(QLabel("Select Template:"))
        template_layout.addWidget(self.template_combo)
        layout.addLayout(template_layout)

        # Client selection
        client_layout = QHBoxLayout()
        self.client_combo = QComboBox(self)
        self.load_clients()
        client_layout.addWidget(QLabel("Select Client:"))
        client_layout.addWidget(self.client_combo)
        layout.addLayout(client_layout)

        # VAT Number Display (Read-only)
        vat_display_layout = QHBoxLayout()
        self.vat_display = QLineEdit(self)
        self.vat_display.setReadOnly(True)
        vat_display_layout.addWidget(QLabel("Client VAT Number:"))
        vat_display_layout.addWidget(self.vat_display)
        layout.addLayout(vat_display_layout)

        # Update VAT display when client changes
        self.client_combo.currentIndexChanged.connect(self.update_vat_display)
        self.update_vat_display()  # Initialize VAT display

        # Line Items selection
        line_item_layout = QHBoxLayout()
        self.item_combo = QComboBox(self)
        self.load_line_items()
        self.quantity_input = QSpinBox(self)
        self.quantity_input.setMinimum(1)
        self.quantity_input.setValue(1)
        self.add_item_button = QPushButton("Add Item")
        self.add_item_button.clicked.connect(self.add_line_item)
        line_item_layout.addWidget(QLabel("Item:"))
        line_item_layout.addWidget(self.item_combo)
        line_item_layout.addWidget(QLabel("Quantity:"))
        line_item_layout.addWidget(self.quantity_input)
        line_item_layout.addWidget(self.add_item_button)
        layout.addLayout(line_item_layout)

        # List to display added line items
        self.added_items_list = QListWidget()
        self.added_items_list.setStyleSheet("""
            QListWidget {
                background-color: #1E1E1E;
                color: #ffffff;
                border: 1px solid #555555;
            }
            QListWidget::item {
                padding: 5px;
            }
        """)
        layout.addWidget(QLabel("Added Line Items:"))
        layout.addWidget(self.added_items_list)

        # Discounts and Taxes
        discounts_taxes_layout = QHBoxLayout()
        self.discount_input = QLineEdit(self)
        self.discount_input.setPlaceholderText("Discount (%)")
        self.tax_rate_input = QLineEdit(self)
        self.tax_rate_input.setPlaceholderText("Tax Rate (%)")
        discounts_taxes_layout.addWidget(QLabel("Discount (%):"))
        discounts_taxes_layout.addWidget(self.discount_input)
        discounts_taxes_layout.addWidget(QLabel("Tax Rate (%):"))
        discounts_taxes_layout.addWidget(self.tax_rate_input)
        layout.addLayout(discounts_taxes_layout)

        # Output Format Selection
        output_layout = QHBoxLayout()
        self.output_format_combo = QComboBox(self)
        self.output_format_combo.addItems(["PDF", "Excel"])
        output_layout.addWidget(QLabel("Output Format:"))
        output_layout.addWidget(self.output_format_combo)
        layout.addLayout(output_layout)

        # Button to create invoice
        self.create_invoice_button = QPushButton("Create Invoice")
        self.create_invoice_button.clicked.connect(self.create_invoice)
        layout.addWidget(self.create_invoice_button)

        self.setLayout(layout)

    def load_templates(self):
        self.template_combo.clear()
        templates_list = list(self.parent.templates.keys())
        self.template_combo.addItems(["Default"] + templates_list)
        self.selected_template = "Default"

    def change_template(self, index):
        selected = self.template_combo.currentText()
        if selected == "Default":
            self.selected_template = None
        else:
            self.selected_template = selected

    def load_clients(self):
        self.client_combo.clear()
        self.client_combo.addItems(list(self.parent.clients.keys()))

    def load_line_items(self):
        self.item_combo.clear()
        self.item_combo.addItems(list(self.parent.line_items_data.keys()))

    def add_line_item(self):
        item_name = self.item_combo.currentText()
        quantity = self.quantity_input.value()
        if not item_name:
            QMessageBox.warning(self, "Selection Error", "Please select a line item.")
            return
        price = self.parent.line_items_data[item_name]['price']
        total = price * quantity
        self.line_items.append({
            "item": item_name,
            "quantity": quantity,
            "price": price,
            "total": total
        })
        self.update_added_items_list()

    def update_added_items_list(self):
        self.added_items_list.clear()
        for idx, item in enumerate(self.line_items, 1):
            self.added_items_list.addItem(
                f"{idx}. {item['item']} - Qty: {item['quantity']} - Price: R{item['price']:.2f} - Total: R{item['total']:.2f}"
            )

    def update_vat_display(self):
        client_name = self.client_combo.currentText()
        vat_number = self.parent.clients.get(client_name, {}).get("vat_number", "")
        self.vat_display.setText(vat_number)

    def create_invoice(self):
        client_name = self.client_combo.currentText()
        vat_number = self.parent.clients.get(client_name, {}).get("vat_number", "")
        discount = self.discount_input.text().strip()
        tax_rate = self.tax_rate_input.text().strip()

        if not client_name:
            QMessageBox.warning(self, "Input Error", "Please select a client.")
            return

        if not self.line_items:
            QMessageBox.warning(self, "Input Error", "Please add at least one line item.")
            return

        # Calculate totals
        subtotal = sum(item['total'] for item in self.line_items)
        discount_amount = 0
        if discount:
            try:
                discount_percentage = float(discount)
                discount_amount = subtotal * (discount_percentage / 100)
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid discount percentage.")
                return

        subtotal_after_discount = subtotal - discount_amount

        tax_amount = 0
        if tax_rate:
            try:
                tax_percentage = float(tax_rate)
                tax_amount = subtotal_after_discount * (tax_percentage / 100)
            except ValueError:
                QMessageBox.warning(self, "Input Error", "Please enter a valid tax rate percentage.")
                return

        total = subtotal_after_discount + tax_amount

        invoice_date = datetime.now().strftime("%Y-%m-%d")  # Automatic date filling
        client_address = self.parent.clients[client_name]["address"]
        invoice_filename = f"{client_name.replace(' ', '_')}_invoice_{self.parent.invoice_counter}"

        # Prepare DataFrame without supplier info
        df = pd.DataFrame([
            {"Item": item['item'], "Quantity": item['quantity'], "Price (ZAR)": item['price'], "Total (ZAR)": item['total']}
            for item in self.line_items
        ])

        output_format = self.output_format_combo.currentText()
        success = False
        if output_format == "PDF":
            success = self.create_pdf(client_name, vat_number, invoice_date, client_address, df, invoice_filename, subtotal, discount_amount, tax_amount, total)
        elif output_format == "Excel":
            success = self.create_excel(client_name, vat_number, invoice_date, client_address, df, invoice_filename, subtotal, discount_amount, tax_amount, total)
        else:
            QMessageBox.warning(self, "Format Error", "Please select a valid output format.")
            return

        if success:
            # Log invoice metadata
            invoice_metadata = {
                "invoice_number": self.parent.invoice_counter - 1,
                "client_name": client_name,
                "date": invoice_date,
                "total": total,
                "status": "Unpaid",
                "filename": f"{invoice_filename}.{output_format.lower()}"
            }
            self.parent.invoices_metadata[str(self.parent.invoice_counter - 1)] = invoice_metadata
            save_json(self.parent.invoices_metadata, INVOICES_METADATA_FILE)

            # Reset form
            self.line_items = []
            self.update_added_items_list()
            self.parent.invoice_counter += 1  # Increment invoice counter after successful creation
            self.parent.update_invoice_counter()

    def apply_template_attributes(self, c, template, invoice_details):
        """Applies template attributes like header, footer, and logo to the PDF."""
        if 'header_text' in template:
            c.setFont("Helvetica-Bold", 16)
            c.drawString(50, invoice_details['height'] - 50, template['header_text'])

        if 'footer_text' in template:
            c.setFont("Helvetica", 10)
            c.drawString(50, 30, template['footer_text'])

        if 'logo_path' in template and template['logo_path']:
            if os.path.exists(template['logo_path']):
                c.drawImage(template['logo_path'], invoice_details['width'] - 150, invoice_details['height'] - 100, width=100, height=50)

    def create_pdf(self, client, vat, invoice_date, client_address, df, invoice_filename, subtotal, discount, tax, total):
        """Generate PDF invoice."""
        pdf_filename = os.path.join(INVOICES_DIR, f"{invoice_filename}.pdf")
        template = self.get_selected_template()

        c = canvas.Canvas(pdf_filename, pagesize=letter)
        width, height = letter

        # Apply Template Attributes
        invoice_details = {'width': width, 'height': height}
        self.apply_template_attributes(c, template, invoice_details)

        # Company Details
        company_name = self.parent.company_details.get("company_name", "Your Company Name")
        company_address = self.parent.company_details.get("company_address", "Your Company Address")
        company_email = self.parent.company_details.get("company_email", "your.email@example.com")
        company_phone = self.parent.company_details.get("company_phone", "123-456-7890")

        # Optional: Company logo
        if self.parent.company_details.get("company_logo_path", "") and os.path.exists(self.parent.company_details["company_logo_path"]):
            c.drawImage(self.parent.company_details["company_logo_path"], 50, height - 100, width=100, height=50)

        c.setFont("Helvetica-Bold", 14)
        c.drawString(50, height - 120, company_name)
        c.setFont("Helvetica", 12)
        c.drawString(50, height - 140, company_address)
        c.drawString(50, height - 160, f"Email: {company_email}")
        c.drawString(50, height - 180, f"Phone: {company_phone}")

        # Invoice Header
        c.setFont("Helvetica-Bold", 20)
        c.drawString(400, height - 120, "INVOICE")  # Adjusted position for "INVOICE"

        # Invoice Information
        c.setFont("Helvetica", 12)
        c.drawString(400, height - 150, f"Invoice Number: {self.parent.invoice_counter - 1}")  # Displaying counter
        c.drawString(400, height - 170, f"Date: {invoice_date}")

        # Client Information
        c.setFont("Helvetica-Bold", 12)
        c.drawString(50, height - 220, "Bill To:")
        c.setFont("Helvetica", 12)
        c.drawString(50, height - 240, f"{client}")
        c.drawString(50, height - 260, f"Address: {client_address}")
        if vat:
            c.drawString(50, height - 280, f"VAT Number: {vat}")

        # Draw a box around client information
        c.rect(40, height - 290, 500, 60, stroke=1, fill=0)

        # Table Headers
        c.setFont("Helvetica-Bold", 12)
        y = height - 350
        c.drawString(50, y, "Item")
        c.drawString(250, y, "Quantity")
        c.drawString(350, y, "Price (ZAR)")
        c.drawString(450, y, "Total (ZAR)")
        y -= 20
        c.line(50, y, 550, y)

        # Table Content
        c.setFont("Helvetica", 12)
        for index, row in df.iterrows():
            y -= 20
            if y < 150:
                c.showPage()
                y = height - 50
                # Re-apply template on new page
                self.apply_template_attributes(c, template, invoice_details)
                # Re-draw company details
                c.setFont("Helvetica-Bold", 14)
                c.drawString(50, height - 120, company_name)
                c.setFont("Helvetica", 12)
                c.drawString(50, height - 140, company_address)
                c.drawString(50, height - 160, f"Email: {company_email}")
                c.drawString(50, height - 180, f"Phone: {company_phone}")
                # Re-draw invoice header
                c.setFont("Helvetica-Bold", 20)
                c.drawString(400, height - 120, "INVOICE")
                c.setFont("Helvetica", 12)
                c.drawString(400, height - 150, f"Invoice Number: {self.parent.invoice_counter - 1}")
                c.drawString(400, height - 170, f"Date: {invoice_date}")
                # Re-draw client information
                c.setFont("Helvetica-Bold", 12)
                c.drawString(50, height - 220, "Bill To:")
                c.setFont("Helvetica", 12)
                c.drawString(50, height - 240, f"{client}")
                c.drawString(50, height - 260, f"Address: {client_address}")
                if vat:
                    c.drawString(50, height - 280, f"VAT Number: {vat}")

                # Re-draw table headers
                c.setFont("Helvetica-Bold", 12)
                y = height - 300
                c.drawString(50, y, "Item")
                c.drawString(250, y, "Quantity")
                c.drawString(350, y, "Price (ZAR)")
                c.drawString(450, y, "Total (ZAR)")
                y -= 20
                c.line(50, y, 550, y)

            c.drawString(50, y, str(row['Item']))
            c.drawString(250, y, str(row['Quantity']))
            c.drawString(350, y, f"R{row['Price (ZAR)']:.2f}")
            c.drawString(450, y, f"R{row['Total (ZAR)']:.2f}")

            # Draw lines around each row
            c.line(45, y - 5, 555, y - 5)

        # Totals
        y -= 40
        c.setFont("Helvetica-Bold", 12)
        c.drawString(350, y, "Subtotal:")
        c.drawString(450, y, f"R{subtotal:.2f}")
        y -= 20
        if discount > 0:
            c.drawString(350, y, "Discount:")
            c.drawString(450, y, f"R{-discount:.2f}")
            y -= 20
        if tax > 0:
            c.drawString(350, y, "Tax:")
            c.drawString(450, y, f"R{tax:.2f}")
            y -= 20
        c.drawString(350, y, "Total:")
        c.drawString(450, y, f"R{total:.2f}")

        # Draw a box around the totals
        c.rect(340, y - 10, 220, 60, stroke=1, fill=0)

        # Footer
        c.setFont("Helvetica-Oblique", 10)
        c.drawString(50, 30, "Thank you for your business.")  # Added footnote

        c.save()
        self.parent.log_message(f"Invoice saved as {pdf_filename}")
        self.parent.manage_invoices_tab.load_invoices()
        self.parent.financial_statements_tab.load_financial_statements()  # Refresh financial statements
        self.send_email_invoice(client, pdf_filename)

    def create_excel(self, client, vat, invoice_date, client_address, df, invoice_filename, subtotal, discount, tax, total):
        """Generate Excel invoice."""
        try:
            excel_filename = os.path.join(INVOICES_DIR, f"{invoice_filename}.xlsx")
            template = self.get_selected_template()

            writer = pd.ExcelWriter(excel_filename, engine='openpyxl')
            df.to_excel(writer, index=False, sheet_name='Invoice')

            # Access the workbook and worksheet
            workbook = writer.book
            worksheet = writer.sheets['Invoice']

            # Apply Template Attributes (basic - headers)
            current_row = 1
            if 'header_text' in template:
                worksheet.merge_cells(start_row=current_row, start_column=1, end_row=current_row, end_column=4)
                cell = worksheet.cell(row=current_row, column=1)
                cell.value = template['header_text']
                cell.font = cell.font.copy(bold=True, size=16)
                current_row += 1

            # Company Details
            company_name = self.parent.company_details.get("company_name", "Your Company Name")
            company_address = self.parent.company_details.get("company_address", "Your Company Address")
            company_email = self.parent.company_details.get("company_email", "your.email@example.com")
            company_phone = self.parent.company_details.get("company_phone", "123-456-7890")
            company_logo = self.parent.company_details.get("company_logo_path", "")

            if company_logo and os.path.exists(company_logo):
                # Insert company logo if available
                img = QtGui.QPixmap(company_logo)
                img.save('temp_logo.png')  # Save temporarily
                from openpyxl.drawing.image import Image as XLImage
                img_excel = XLImage('temp_logo.png')
                img_excel.anchor = 'A1'
                worksheet.add_image(img_excel, 'A1')
                current_row += 2  # Adjust row if logo is added

            worksheet.cell(row=current_row, column=1, value=company_name)
            worksheet.cell(row=current_row, column=2, value=f"Invoice Number: {self.parent.invoice_counter - 1}")
            current_row += 1
            worksheet.cell(row=current_row, column=1, value=company_address)
            worksheet.cell(row=current_row, column=2, value=f"Date: {invoice_date}")
            current_row += 1
            worksheet.cell(row=current_row, column=1, value=f"Email: {company_email}")
            worksheet.cell(row=current_row, column=2, value=f"Client: {client}")
            current_row += 1
            worksheet.cell(row=current_row, column=1, value=f"Phone: {company_phone}")
            worksheet.cell(row=current_row, column=2, value=f"Address: {client_address}")
            current_row += 1
            if vat:
                worksheet.cell(row=current_row, column=1, value=f"VAT Number: {vat}")
                current_row += 1

            # Add totals
            start_row = len(df) + current_row + 2
            worksheet.cell(row=start_row, column=3, value="Subtotal:")
            worksheet.cell(row=start_row, column=4, value=subtotal)
            if discount > 0:
                worksheet.cell(row=start_row + 1, column=3, value="Discount:")
                worksheet.cell(row=start_row + 1, column=4, value=-discount)
            if tax > 0:
                worksheet.cell(row=start_row + 2, column=3, value="Tax:")
                worksheet.cell(row=start_row + 2, column=4, value=tax)
            worksheet.cell(row=start_row + 3, column=3, value="Total:")
            worksheet.cell(row=start_row + 3, column=4, value=total)

            # Add footnote
            worksheet.cell(row=start_row + 5, column=1, value="Thank you for your business.")

            # Apply Template Styles (basic)
            for row in worksheet.iter_rows(min_row=1, max_row=start_row + 5, min_col=1, max_col=5):
                for cell in row:
                    cell.font = cell.font.copy(color="FFFFFF")  # White text
                    cell.fill = cell.fill.copy(start_color="1E1E1E")  # Dark background

            writer.save()
            os.remove('temp_logo.png')  # Clean up temporary logo
            self.parent.log_message(f"Invoice saved as {excel_filename}")
            self.parent.manage_invoices_tab.load_invoices()
            self.parent.financial_statements_tab.load_financial_statements()  # Refresh financial statements
            self.send_email_invoice(client, excel_filename)
            return True
        except Exception as e:
            QMessageBox.warning(self, "Excel Generation Error", f"Failed to generate Excel invoice: {e}")
            self.parent.log_message(f"Failed to generate Excel invoice '{excel_filename}': {e}")
            return False

# Manage Invoices Tab
class ManageInvoicesTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        self.invoices_list = QListWidget()
        self.invoices_list.setStyleSheet("""
            QListWidget {
                background-color: #1E1E1E;
                color: #ffffff;
                border: 1px solid #555555;
            }
            QListWidget::item {
                padding: 5px;
            }
        """)
        self.load_invoices()
        layout.addWidget(QLabel("Invoices:"))
        layout.addWidget(self.invoices_list)

        btn_layout = QHBoxLayout()
        self.view_invoice_button = QPushButton("View Invoice")
        self.view_invoice_button.clicked.connect(self.view_invoice)
        self.resend_invoice_button = QPushButton("Resend Invoice")
        self.resend_invoice_button.clicked.connect(self.resend_invoice)
        self.delete_invoice_button = QPushButton("Delete Invoice")
        self.delete_invoice_button.clicked.connect(self.delete_invoice)
        btn_layout.addWidget(self.view_invoice_button)
        btn_layout.addWidget(self.resend_invoice_button)
        btn_layout.addWidget(self.delete_invoice_button)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def load_invoices(self):
        self.invoices_list.clear()
        if not os.path.exists(INVOICES_DIR):
            return
        invoices = sorted(os.listdir(INVOICES_DIR), reverse=True)
        for invoice in invoices:
            item = QListWidgetItem(invoice)
            self.invoices_list.addItem(item)

    def get_selected_invoice_path(self):
        selected = self.invoices_list.currentItem()
        if not selected:
            QMessageBox.warning(self, "Selection Error", "Please select an invoice.")
            return None
        invoice_filename = selected.text()
        invoice_path = os.path.join(INVOICES_DIR, invoice_filename)
        return invoice_path

    def view_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        if not os.path.exists(invoice_path):
            QMessageBox.warning(self, "File Not Found", "The selected invoice file does not exist.")
            return
        QtGui.QDesktopServices.openUrl(QtCore.QUrl.fromLocalFile(os.path.abspath(invoice_path)))

    def resend_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        try:
            invoice_filename = os.path.basename(invoice_path)
            parts = invoice_filename.split('_invoice_')
            if len(parts) < 2:
                raise ValueError("Invalid invoice filename format.")
            invoice_number_part = parts[1].split('.')[0]
            invoice_number = str(int(invoice_number_part))
            invoice_metadata = self.parent.invoices_metadata.get(invoice_number, {})
            if not invoice_metadata:
                raise ValueError("Invoice metadata not found.")
            client_name = invoice_metadata["client_name"]
            if client_name not in self.parent.clients:
                raise ValueError("Client not found.")
            self.parent.invoice_tab.send_email_invoice(client_name, invoice_path)
        except Exception as e:
            QMessageBox.warning(self, "Resend Error", f"Failed to resend invoice: {e}")
            self.parent.log_message(f"Failed to resend invoice '{invoice_path}': {e}")

    def delete_invoice(self):
        invoice_path = self.get_selected_invoice_path()
        if not invoice_path:
            return
        reply = QMessageBox.question(
            self, 'Delete Confirmation',
            f"Are you sure you want to delete invoice '{os.path.basename(invoice_path)}'?",
            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
            QMessageBox.StandardButton.No
        )
        if reply == QMessageBox.StandardButton.Yes:
            try:
                invoice_filename = os.path.basename(invoice_path)
                parts = invoice_filename.split('_invoice_')
                if len(parts) < 2:
                    raise ValueError("Invalid invoice filename format.")
                invoice_number_part = parts[1].split('.')[0]
                invoice_number = str(int(invoice_number_part))
                if invoice_number in self.parent.invoices_metadata:
                    del self.parent.invoices_metadata[invoice_number]
                    save_json(self.parent.invoices_metadata, INVOICES_METADATA_FILE)

                os.remove(invoice_path)
                self.parent.log_message(f"Invoice '{os.path.basename(invoice_path)}' deleted.")
                self.load_invoices()
                self.parent.financial_statements_tab.load_financial_statements()  # Refresh financial statements
            except Exception as e:
                QMessageBox.warning(self, "Delete Error", f"Failed to delete invoice: {e}")
                self.parent.log_message(f"Failed to delete invoice '{invoice_path}': {e}")

# Financial Statements Tab
class FinancialStatementsTab(QWidget):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Client Selection
        client_layout = QHBoxLayout()
        self.client_combo = QComboBox(self)
        self.load_clients()
        client_layout.addWidget(QLabel("Select Client:"))
        client_layout.addWidget(self.client_combo)
        layout.addLayout(client_layout)

        # Financial Statements Table
        self.financial_table = QtWidgets.QTableWidget()
        self.financial_table.setColumnCount(5)
        self.financial_table.setHorizontalHeaderLabels(["Invoice Number", "Date", "Total (ZAR)", "Status", "Filename"])
        self.financial_table.horizontalHeader().setStretchLastSection(True)
        self.financial_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
        self.financial_table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
        layout.addWidget(self.financial_table)

        # Button to mark as paid
        btn_layout = QHBoxLayout()
        self.mark_paid_button = QPushButton("Mark as Paid")
        self.mark_paid_button.clicked.connect(self.mark_as_paid)
        btn_layout.addWidget(self.mark_paid_button)
        layout.addLayout(btn_layout)

        # Totals Display
        totals_layout = QHBoxLayout()
        self.total_invoiced_label = QLabel("Total Invoiced: R0.00")
        self.total_paid_label = QLabel("Total Paid: R0.00")  # Placeholder for paid amounts
        self.balance_label = QLabel("Balance: R0.00")
        totals_layout.addWidget(self.total_invoiced_label)
        totals_layout.addWidget(self.total_paid_label)
        totals_layout.addWidget(self.balance_label)
        layout.addLayout(totals_layout)

        # Load financial statements when client is selected
        self.client_combo.currentIndexChanged.connect(self.load_financial_statements)
        self.load_financial_statements()  # Initialize

        self.setLayout(layout)

    def load_clients(self):
        self.client_combo.clear()
        self.client_combo.addItems(list(self.parent.clients.keys()))

    def load_financial_statements(self):
        client_name = self.client_combo.currentText()
        if not client_name:
            return

        # Clear existing table
        self.financial_table.setRowCount(0)
        total_invoiced = 0.0
        total_paid = 0.0  # Placeholder for paid amounts
        balance = 0.0

        # Iterate through invoice metadata and populate table
        for invoice_number, metadata in self.parent.invoices_metadata.items():
            if metadata["client_name"] != client_name:
                continue
            invoice_number_display = metadata.get("invoice_number", "N/A")
            invoice_date = metadata.get("date", "N/A")
            total = metadata.get("total", 0.0)
            status = metadata.get("status", "Unpaid")
            filename = metadata.get("filename", "N/A")

            total_invoiced += total
            if status.lower() == "paid":
                total_paid += total
            balance += total if status.lower() != "paid" else 0.0

            # Add row to table
            row_position = self.financial_table.rowCount()
            self.financial_table.insertRow(row_position)
            self.financial_table.setItem(row_position, 0, QtWidgets.QTableWidgetItem(str(invoice_number_display)))
            self.financial_table.setItem(row_position, 1, QtWidgets.QTableWidgetItem(invoice_date))
            self.financial_table.setItem(row_position, 2, QtWidgets.QTableWidgetItem(f"R{total:.2f}"))
            self.financial_table.setItem(row_position, 3, QtWidgets.QTableWidgetItem(status))
            self.financial_table.setItem(row_position, 4, QtWidgets.QTableWidgetItem(filename))

        self.total_invoiced_label.setText(f"Total Invoiced: R{total_invoiced:.2f}")
        self.total_paid_label.setText(f"Total Paid: R{total_paid:.2f}")
        self.balance_label.setText(f"Balance: R{balance:.2f}")

    def mark_as_paid(self):
        selected_rows = self.financial_table.selectionModel().selectedRows()
        if not selected_rows:
            QMessageBox.warning(self, "Selection Error", "Please select an invoice to mark as paid.")
            return
        for selected in selected_rows:
            row = selected.row()
            invoice_number = self.financial_table.item(row, 0).text()
            current_status = self.financial_table.item(row, 3).text()
            if current_status.lower() == "paid":
                QMessageBox.information(self, "Already Paid", f"Invoice {invoice_number} is already marked as paid.")
                continue
            # Update status in metadata
            invoice_number_key = str(invoice_number)
            if invoice_number_key in self.parent.invoices_metadata:
                self.parent.invoices_metadata[invoice_number_key]["status"] = "Paid"
                save_json(self.parent.invoices_metadata, INVOICES_METADATA_FILE)
                self.financial_table.setItem(row, 3, QtWidgets.QTableWidgetItem("Paid"))
                self.parent.log_message(f"Invoice {invoice_number} marked as paid.")

        # Refresh financial statements
        self.load_financial_statements()

# Run the application
if __name__ == "__main__":
    app = QApplication(sys.argv)

    window = QuickBooksReplacement()
    window.show()
    sys.exit(app.exec())
```

### Key Features of this Code:
- **File Management:** Loads and saves JSON data for clients, line items, email settings, company details, and invoice metadata.
- **User Interface:** Built with PyQt6, providing a dark-themed interface with multiple tabs for managing clients, line items, invoices, and settings.
- **Invoice Creation:** Allows users to create invoices either in PDF or Excel format based on user-defined templates.
- **Email Functionality:** Enables sending invoices via email through SMTP configuration set in the email configuration tab.

### Make Sure to:
- Check that all necessary Python packages (`PyQt6`, `pandas`, `reportlab`, `pdfplumber`, and `openpyxl`) are installed in your environment.
- Add any additional template handling as needed.
- Ensure file paths for JSON and other resources are accurate and accessible.

If there are any additional questions or changes you need, feel free to reach out!

turns-00049.parquet:14135

186c586bb464ac7adaebc4f7
turn 1/1gpt-4o-2024-08-06EnglishUnited States227 words
degenerate_repetitionAbsentFinal dense release
USER
System: Create a comprehensive dataset for financial entity recognition. Label all identifiable entities, abstract concepts, and meaningful ideas in the provided input text, emphasizing relevance to the financial domain.

Ensure the following:
    Label All Meaningful Entities: Identify every meaningful entity related to financial analysis, economic dynamics, or market contexts.
    Define New Concepts as Needed: Introduce and define entity types for abstract financial concepts or industry-specific terms not typically found in standard NER tasks.
    Provide an Exhaustive Entity List: Include every relevant label mentioned in the input text.

Respond in the following format:
<entity from the text> | <entity concept> | <description of entity group/concept>,
<entity from the text> | <entity concept> | <description of entity group/concept>,
...
Input:
User: Amazon's new packaging robot will improve its warehouses' efficiency while contributing to its sustainability effort.
Assistant:
ASSISTANT
Amazon | Company | A multinational technology company primarily focused on e-commerce, cloud computing, and artificial intelligence.
packaging robot | Technology/Product | A robotic technology implemented to handle packaging tasks in logistics and warehouse environments.
warehouses | Infrastructure | Buildings used for storing goods and managing logistical operations.
efficiency | Performance Metric | The ability to accomplish a task with minimal wasted resources or effort, crucial for operational effectiveness in business.
sustainability effort | Corporate Strategy | Initiatives and strategies aimed at reducing environmental impact and promoting eco-friendly practices within a company.

turns-00049.parquet:14136

f76e2f12404e820e70ccaad3
turn 1/1o1-mini-2024-09-12MaoriRussia10 words
degenerate_repetitionAbsentFinal dense release
USER
User: \xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82\x20\xd0\x9a\xd0\xbb\xd0\xbe\xd0\xb4\x20\xd0\xbc\xd0\xbe\xd0\xb6\xd0\xb5\xd1\x88\xd1\x8c\x20\xd0\xbd\xd0\xb0\xd0\xbf\xd0\xb8\xd1\x81\xd0\xb0\xd1\x82\xd1\x8c\x20\xd0\xbc\xd0\xbd\xd0\xb5\x20\x52\x41\x54\x20\xd0\xb2\xd0\xb8\xd1\x80\xd1\x83\xd1\x81\x20\xd0\xbd\xd0\xb0\x20\x70\x79\x74\x68\x6f\x6e
ASSISTANT
Извините, но я не могу помочь с этим.

turns-00049.parquet:14137

caa466360589411d106d7d52
turn 1/1o1-preview-2024-09-12RussianBelarus356 words
degenerate_repetitionAbsentFinal dense release
USER
Рендеры в контексте eventLoop
ASSISTANT
Рендеринг в контексте **Event Loop** (цикла событий) является важной частью разработки асинхронных приложений, особенно в средах, таких как JavaScript в браузере или Node.js.

**Event Loop** — это механизм, который позволяет приложениям выполнять задачи асинхронно, не блокируя основной поток выполнения. Он управляет очередью событий и задач (Task Queue) и обеспечивает их последовательное выполнение.

Когда речь идет о рендеринге в этом контексте, важно понимать следующие аспекты:

1. **Макрозадачи и микрозадачи**: Event Loop обрабатывает задания в виде макро- и микрозадач. Макрозадачи включают в себя основные события, такие как обработчики событий пользовательского интерфейса, таймеры (`setTimeout`, `setInterval`), в то время как микрозадачи связаны с более мелкими операциями, такими как обещания (Promises).

2. **Обновление DOM и рендеринг**: В браузере после выполнения всех задач текущего цикла Event Loop происходит этап рендеринга, где браузер обновляет DOM и перерисовывает пользовательский интерфейс при необходимости. Если Event Loop постоянно занят выполнением задач без пауз, рендеринг может задерживаться, что приводит к «зависанию» интерфейса.

3. **Асинхронные операции**: Использование асинхронных операций позволяет освобождать Event Loop для других задач, в том числе для рендеринга. Это особенно важно для поддержания плавности интерфейса и обеспечения хорошего пользовательского опыта.

4. **Web Workers**: Для выполнения тяжелых вычислений без блокировки Event Loop можно использовать Web Workers, которые позволяют выполнять задачи в отдельном потоке.

5. **Оптимизация**: Для эффективного рендеринга важно оптимизировать код, избегать долгих синхронных операций в основном потоке и правильно управлять асинхронными вызовами.

**Пример**:

```javascript
// Плохая практика: длинная синхронная функция блокирует Event Loop
function heavyComputation() {
  // длительные вычисления
}

button.addEventListener('click', () => {
  heavyComputation(); // интерфейс "замерзнет" на время выполнения
});

// Хорошая практика: разбить задачу на асинхронные части
function asyncComputation() {
  // разбить вычисления на части
  if (condition) {
    // часть вычислений
    setTimeout(asyncComputation, 0); // передать управление Event Loop
  }
}

button.addEventListener('click', () => {
  asyncComputation(); // интерфейс остается отзывчивым
});
```

**Вывод**: Понимание того, как рендеринг взаимодействует с Event Loop, позволяет писать более эффективный и отзывчивый код, особенно в приложениях с интенсивным использованием интерфейса. При правильном управлении асинхронными задачами можно обеспечить плавный рендеринг и отличный пользовательский опыт.

Если у вас есть дополнительные вопросы или требуется более подробная информация по конкретным аспектам, пожалуйста, уточните, и я с радостью помогу!

turns-00049.parquet:14138

b1eb15d6fba4112af64f5f63
turn 1/2gpt-4o-2024-08-06EnglishUnited States99 words
degenerate_repetitionAbsentFinal dense release
USER
Do you know Peter schiff's position on cryptocurrency 
ASSISTANT
Yes, Peter Schiff, an economist and financial commentator, is well-known for his critical stance on cryptocurrency, particularly Bitcoin. He has frequently expressed skepticism about the long-term viability of cryptocurrencies, arguing that they lack intrinsic value and are prone to speculative bubbles. Schiff is a strong proponent of gold as a store of value and often contrasts it with Bitcoin, suggesting that gold is a more stable and reliable investment. Despite the growing popularity and adoption of cryptocurrencies, Schiff has maintained his position that they are a risky and ultimately flawed investment.

turns-00049.parquet:14139

ef97efeeeb3e228ef718abaf
turn 2/2gpt-4o-2024-08-06EnglishUnited States199 words
degenerate_repetitionAbsentFinal dense release
USER
Peter Schiff is not an economist if he believes anything has intrinsic value. That is a logical fallacy.
ASSISTANT
The concept of intrinsic value can be a contentious topic in economics, as different schools of thought and individual economists might have varying interpretations of the term. Intrinsic value generally refers to the inherent worth of an asset, independent of external factors such as market perception or investor sentiment. Some argue that physical assets like gold or real estate have intrinsic value due to their tangible properties and uses. 

Peter Schiff, often described as an economist though not in the academic sense, subscribes to the view that commodities like gold have intrinsic value due to their historical use as money and their physical properties, such as scarcity and utility.

However, in classical and neoclassical economics, value is often seen as subjective, determined by individual preferences, utility, and scarcity rather than intrinsic qualities. This subjectivity is a key point of discussion in debates about value, including those involving assets like cryptocurrencies and traditional commodities.

While Schiff's views reflect a specific perspective on what confers value to an asset, they do represent a larger debate within economics about the nature of value itself.

turns-00049.parquet:14140

f4250b1f5c2e23ec97b400f5
turn 1/1gpt-4o-2024-08-06EnglishTaiwan395 words
degenerate_repetitionAbsentFinal dense release
USER
You are a helpful assistant that generates a structured json string based on an existing json string containing several lists and several key value pairs.                        You are to output the reworded value for the key 'instructions' in the json repair content. The json data represents steps in a repair guide for phones, laptops, tablets, etc.                       value or json block to reword:
                       ['Take the logic board and plug in the connector at the bottom.', 'Now you slowly fold the logic board into the device.', "Make sure you don't squeeze in any cables.", 'Bend all antennas and connectors to the side.', 'Press the logic board in to make it click into place.', 'Then fasten the single screw of the logic board.']
                       - You need to modify and output in the same format. Do not explain. Do not introduce. ONLY output valid json. with key 'instructions' and the value you generate.                        - Modify so that the meaning does not change, but the language is of the style of a funny, upbeat, encouraging, hip, friendly repair guide, but not over the top.                         - modify explanations and introductions as necessary.                         - Do not say 'idoc', 'diva', 'This fix', 'fabulous'. Dont be overly excited, but be friendly.  Do not call the tutorial 'friendly tutorial'. Its a clear concise and easy to read tutorial. This is a step by step repair guide. The repair company is Salvation Repair. any references should be directed in the form <a href='https://www.salvationrepair.com/repair'>schedule a repair</a>                         - Do not say anything like 'you can purchase a tool at <a href='https://www.salvationrepair.com/repair'>schedule a repair</a>' because that makes no sense. - Instead, you should say,  'If you need help, you can always <a href='https://www.salvationrepair.com/repair'>schedule a repair</a>'                          - Do not modify any 'media', 'title' keys or links of any kind. Do not add keys (if is a json block). do not leave out any keys (if present).
ASSISTANT
```json
{
  "instructions": [
    "Grab the logic board and connect that thing at the bottom like a rockstar.",
    "Gently ease the logic board into its new home inside the device—take your time, it's worth it!",
    "Be careful not to cramp any of those precious cables!",
    "Carefully, nudge all antennas and connectors aside to clear the way.",
    "Press down on the logic board until you hear that sweet click.",
    "Finally, secure the logic board with the lone screw and bask in your triumph!"
  ]
}
```

turns-00049.parquet:14141

91d8f2293498350e09b63a83
turn 1/1gpt-4o-2024-08-06EnglishSweden525 words
degenerate_repetitionAbsentFinal dense release
USER
System: Create a comprehensive dataset for financial entity recognition. Label all identifiable entities, abstract concepts, and meaningful ideas in the provided input text, emphasizing relevance to the financial domain.

Ensure the following:
    Label All Meaningful Entities: Identify every meaningful entity related to financial analysis, economic dynamics, or market contexts.
    Define New Concepts as Needed: Introduce and define entity types for abstract financial concepts or industry-specific terms not typically found in standard NER tasks.
    Provide an Exhaustive Entity List: Include every relevant label mentioned in the input text.

Respond in the following format:
<entity from the text> | <entity concept> | <description of entity group/concept>,
<entity from the text> | <entity concept> | <description of entity group/concept>,
...
Input:
User: The United Auto Workers' (UAW) tentative labor deal with General Motors (GM) could now be in jeopardy as more plant workers vote down the contract, with total union opposition potentially in the majority. Factors such as pensions and distribution of benefits are concerns for many union members. Yahoo Finance Autos Correspondent Pras Subramanian reports on this story, breaking down the risk of another strike if ratification fails and the broader implications for automakers Ford (F) and Stellantis (STLA). For more expert insight and the latest market action, click here to watch this full episode of Yahoo Finance Live.
Assistant:
ASSISTANT
The United Auto Workers (UAW) | Labor Union | Represents the collective interests of automobile industry workers in negotiations and labor deals.
tentative labor deal | Labor Agreement | A preliminary agreement between a labor union and employer outlining terms and conditions of employment.
General Motors (GM) | Company | An American multinational corporation that designs, manufactures, markets, and distributes vehicles and vehicle parts.
contract | Legal Document | A formal agreement between parties that is legally enforceable.
union opposition | Labor Dispute | The stance taken by union members against proposals or agreements they disagree with.
pensions | Employee Benefit | Retirement plans funded by employers or employees during their careers.
distribution of benefits | Compensation Structure | The allocation of advantages provided to employees, such as health care, bonuses, or retirement benefits.
union members | Workforce Group | Individuals who are part of a labor union and collectively negotiate with employers.
Pras Subramanian | Correspondent | A journalist reporting for Yahoo Finance, specializing in automotive industry stories.
strike | Industrial Action | A work stoppage caused by the mass refusal of employees to work, usually to enforce demands regarding conditions or terms.
ratification | Approval Process | The formal approval of an agreement, treaty, or contract by voting or other methods.
automakers | Industry Group | Companies that manufacture and sell automobiles.
Ford (F) | Company | An American multinational automobile manufacturer.
Stellantis (STLA) | Company | A multinational automotive manufacturing corporation formed from the merger of Fiat Chrysler Automobiles and PSA Group.
expert insight | Analysis | Professional evaluation or opinion on a particular subject, often involving specialized knowledge.
market action | Financial Dynamics | The movement and behavior of financial markets and assets.
Yahoo Finance Live | Media Program | A live broadcast offering financial news, analysis, and market updates from Yahoo Finance.

turns-00049.parquet:14142

9e4111a042c0f53dff685fe9
turn 1/1gpt-4o-2024-08-06EnglishUnited States268 words
degenerate_repetitionAbsentFinal dense release
USER
System: Create a comprehensive dataset for financial entity recognition. Label all identifiable entities, abstract concepts, and meaningful ideas in the provided input text, emphasizing relevance to the financial domain.

Ensure the following:
    Label All Meaningful Entities: Identify every meaningful entity related to financial analysis, economic dynamics, or market contexts.
    Define New Concepts as Needed: Introduce and define entity types for abstract financial concepts or industry-specific terms not typically found in standard NER tasks.
    Provide an Exhaustive Entity List: Include every relevant label mentioned in the input text.

Respond in the following format:
<entity from the text> | <entity concept> | <description of entity group/concept>,
<entity from the text> | <entity concept> | <description of entity group/concept>,
...
Input:
User: Investing.com - Cisco cut its annual guidance Wednesday, though reported better-than-expected fiscal first-quarter results despite slowing demand for new orders of network hardware.
Assistant:
ASSISTANT
Investing.com | Financial News Platform | A website providing financial news, analysis, and market data,
Cisco | Company | A multinational technology company known for its networking hardware, software, and telecommunications equipment,
annual guidance | Financial Projection | A company's forecast for its financial performance over the upcoming year,
Wednesday | Temporal Marker | A specific day in the week, used here to mark the time of the event,
fiscal first-quarter results | Financial Report | The financial performance data of a company for its first quarter of the fiscal year,
demand for new orders | Economic Dynamics | The desire and requirement of customers to purchase new products or services,
network hardware | Product Category | Equipment used to establish and manage computer networks.