Invoice Generator

A desktop application designed to generate customized PDF invoices based on user input. The program provides an user-friendly interface built with CustomTkinter, allowing users to easily enter customer details, product information, and pricing data. Once completed, the app automatically creates a professional PDF invoice from a Word template and saves all invoice details to a MySQL database for record-keeping.

Python
import tkinter as tk 

from tkinter import messagebox, filedialog 

import docx 

import datetime as dt 

from docx2pdf import convert 

import os 

import customtkinter as ctk 

from sqlalchemy import create_engine, Column, String, Integer, Date, Float 

from sqlalchemy.orm import sessionmaker, declarative_base 

 

# SQLAlchemy setup 

Base = declarative_base() 

engine = create_engine("xxxxxxxxxxxxxxxxxxxxxx") 

Session = sessionmaker(bind=engine) 

 

# ORM model representing the invoices table in the database. Defines the structure of stored invoice records. 

class Invoice(Base):  

    __tablename__ = "invoices" 

 

    id = Column(Integer, primary_key=True, autoincrement=True) 

    customer_name = Column(String(64)) 

    customer_address = Column(String(64)) 

    customer_vat_id = Column(String(64)) 

    invoice_number = Column(String(64)) 

    product = Column(String(64)) 

    quantity = Column(Integer) 

    price = Column(Float) 

    vat_rate = Column(Float) 

    netto = Column(Float) 

    tax_amount = Column(Float) 

    total = Column(Float) 

    payment_terms = Column(String(64)) 

    invoice_date = Column(Date) 

 

Base.metadata.create_all(engine) 

 

# Service class responsible for invoice-related business logic, including calculations, document generation, PDF conversion, 

# and database persistence. 

class InvoiceManager: 

    # Calculates invoice financial values (net amount, tax amount, and total). 

    def calculate_totals(self, quantity, price, vat_rate): 

        netto = round(quantity * price, 2) 

        tax_amount = round(netto * (vat_rate / 100), 2) 

        total = netto + tax_amount 

        return netto, tax_amount, total 

 

    # Generates an invoice document based on a Word template and replaces placeholders with actual invoice data. 

    def generate_invoice_docx(self, template_path, replacements, quantity, price, vat_rate, total, product): 

        doc = docx.Document(template_path) 

 

        for paragraph in doc.paragraphs: 

            for old_text, new_text in replacements.items(): 

                if old_text in paragraph.text: 

                    paragraph.text = paragraph.text.replace(old_text, str(new_text)) 

 

        for table in doc.tables: 

            for row in table.rows: 

                for cell in row.cells: 

                    if "[Product]" in cell.text: 

                        cell.text = cell.text.replace("[Product]", str(product)) 

                    if "[Quantity]" in cell.text: 

                        cell.text = cell.text.replace("[Quantity]", str(quantity)) 

                    if "[Price]" in cell.text: 

                        cell.text = cell.text.replace("[Price]", str(price)) 

                    if '[Tax rate]' in cell.text: 

                        cell.text = cell.text.replace('[Tax rate]', str(vat_rate)) 

                    if "[Total Price]" in cell.text: 

                        cell.text = cell.text.replace("[Total Price]", str(total)) 

 

        return doc 

 

    # Converts the generated DOCX invoice into a PDF file. A temporary DOCX file is created and removed after conversion. 

    def convert_to_pdf(self, doc, save_path): 

        temp_docx = save_path.replace('.pdf', '.docx') 

        doc.save(temp_docx) 

        convert(temp_docx, save_path) 

        if os.path.exists(temp_docx): 

            os.remove(temp_docx) 

 

    # Saves invoice data into the database using SQLAlchemy ORM. 

    def save_to_database(self, data): 

        session = Session() 

        invoice_record = Invoice( 

            customer_name=data['Customer Name'], 

            customer_address=data['Customer Address'], 

            customer_vat_id=data['Customer VAT ID'], 

            invoice_number=data['Invoice Number'], 

            product=data['Product'], 

            quantity=data['Quantity'], 

            price=data['Price'], 

            vat_rate=data['Vat rate'], 

            netto=data['Netto'], 

            tax_amount=data['Tax Amount'], 

            total=data['Invoice Total'], 

            payment_terms=data['Payment Terms'], 

            invoice_date=data['Invoice Date'] 

        ) 

        session.add(invoice_record) 

        session.commit() 

        session.close() 

 

# GUI class responsible for user interaction and input collection. It connects the interface with the application business logic. 

class InvoiceCreator: 

    def __init__(self): 

        self.manager = InvoiceManager()  

 

        self.root = ctk.CTk() 

        self.root.title("Invoice PDF Creator") 

        self.root.geometry('410x510') 

 

        ctk.set_appearance_mode("dark") 

        ctk.set_default_color_theme("dark-blue") 

 

        self.Buyer_name = ctk.CTkLabel(self.root, text="Customer Name", font=('Segoe UI', 13)) 

        self.Buyer_name.grid(row=0, column=0, columnspan=3, padx=5) 

        self.Buyer_name_entry = ctk.CTkEntry(self.root, width=400) 

        self.Buyer_name_entry.grid(row=1, column=0, columnspan=3, padx=5) 

 

        self.Buyer_address = ctk.CTkLabel(self.root, text="Customer Address", font=('Segoe UI', 13)) 

        self.Buyer_address.grid(row=2, column=0, columnspan=3, padx=5) 

        self.Buyer_address_entry = ctk.CTkEntry(self.root, width=400) 

        self.Buyer_address_entry.grid(row=3, column=0, columnspan=3, padx=5) 

 

        self.Buyer_vat_id = ctk.CTkLabel(self.root, text="Customer VAT ID", font=('Segoe UI', 13)) 

        self.Buyer_vat_id.grid(row=4, column=0, columnspan=3, padx=5) 

        self.Buyer_vat_id_entry = ctk.CTkEntry(self.root, width=400) 

        self.Buyer_vat_id_entry.grid(row=5, column=0, columnspan=3, padx=5) 

 

        self.Invoice_nr = ctk.CTkLabel(self.root, text="Invoice Number", font=('Segoe UI', 13)) 

        self.Invoice_nr.grid(row=6, column=0, columnspan=3, padx=5) 

        self.Invoice_nr_entry = ctk.CTkEntry(self.root, width=400) 

        self.Invoice_nr_entry.grid(row=7, column=0, columnspan=3, padx=5) 

 

        self.Product = ctk.CTkLabel(self.root, text="Product", font=('Segoe UI', 13)) 

        self.Product.grid(row=8, column=0, columnspan=3, padx=5) 

        self.Product_entry = ctk.CTkEntry(self.root, width=400) 

        self.Product_entry.grid(row=9, column=0, columnspan=3, padx=5) 

 

        self.values_frame = ctk.CTkFrame(self.root) 

        self.values_frame.grid(row=10, column=0, columnspan=3, pady=(10, 0)) 

 

        self.Quantity = ctk.CTkLabel(self.values_frame, text="Qty", font=('Segoe UI', 13)) 

        self.Quantity.grid(row=0, column=0, padx=5) 

        self.Quantity_entry = ctk.CTkEntry(self.values_frame, width=90) 

        self.Quantity_entry.grid(row=1, column=0, padx=5) 

 

        self.Price = ctk.CTkLabel(self.values_frame, text="Price", font=('Segoe UI', 13)) 

        self.Price.grid(row=0, column=1, padx=5) 

        self.Price_entry = ctk.CTkEntry(self.values_frame, width=90) 

        self.Price_entry.grid(row=1, column=1, padx=5) 

 

        self.Vat_rate = ctk.CTkLabel(self.values_frame, text="Tax (%)", font=('Segoe UI', 13)) 

        self.Vat_rate.grid(row=0, column=2, padx=5) 

        self.Vat_rate_entry = ctk.CTkEntry(self.values_frame, width=90) 

        self.Vat_rate_entry.grid(row=1, column=2, padx=5) 

 

        self.payment_terms_label = ctk.CTkLabel(self.root, text='Payment Terms', font=('Segoe UI', 13)) 

        self.payment_terms_label.grid(row=12, column=0, columnspan=3, padx=5, pady=5) 

 

        self.payment_terms_var = tk.StringVar(self.root) 

        self.payment_terms_var.set("Immediate") 

 

        self.payment_terms_dropdown = ctk.CTkOptionMenu( 

            self.root, 

            variable=self.payment_terms_var, 

            values=["Immediate", "Net7", "Net30"], 

            width=200, 

            anchor='center', 

            font=('Segoe UI', 11) 

        ) 

        self.payment_terms_dropdown.grid(row=13, column=0, columnspan=3, padx=5) 

 

        self.button_create = ctk.CTkButton( 

            self.root, text='CREATE INVOICE', font=('Segoe UI', 16), width=350, 

            command=self.create_invoice 

        ) 

        self.button_create.grid(row=14, column=1, padx=15, pady=25) 

 

        self.root.mainloop() 

 

    # Handles the full invoice creation workflow: input validation, calculation, document generation, PDF export, and database persistence. 

    def create_invoice(self): 

        try: 

            quantity = float(self.Quantity_entry.get()) 

            price = float(self.Price_entry.get()) 

            vat_rate = float(self.Vat_rate_entry.get()) 

            netto, tax_amount, total = self.manager.calculate_totals(quantity, price, vat_rate) 

 

            replacements = { 

                "[Customer Name]": self.Buyer_name_entry.get(), 

                "[Invoice Date]": dt.date.today(), 

                "[Customer Address]": self.Buyer_address_entry.get(), 

                "[Customer VAT ID]": self.Buyer_vat_id_entry.get(), 

                "[Invoice Number]": self.Invoice_nr_entry.get(), 

                "[Vat rate]": vat_rate, 

                "[Netto]": f'{netto} PLN', 

                "[Tax Amount]": f'{tax_amount} PLN', 

                "[Invoice Total]": f'{total} PLN', 

                "[Payment Terms]": self.payment_terms_var.get(), 

                "[Account]": 'AA123456789' 

            } 

 

            doc = self.manager.generate_invoice_docx( 

                r"templates\Invoice_Dino_Template.docx", 

                replacements, 

                quantity, 

                price, 

                vat_rate, 

                total, 

                self.Product_entry.get() 

            ) 

 

            save_path = filedialog.asksaveasfilename( 

                initialfile=f"{self.Invoice_nr_entry.get()}.pdf", 

                defaultextension='.pdf', 

                filetypes=[('PDF files', '*.pdf')] 

            ) 

 

            if save_path: 

                self.manager.convert_to_pdf(doc, save_path) 

 

                data_to_save = { 

                    'Customer Name': self.Buyer_name_entry.get(), 

                    'Customer Address': self.Buyer_address_entry.get(), 

                    'Customer VAT ID': self.Buyer_vat_id_entry.get(), 

                    'Invoice Number': self.Invoice_nr_entry.get(), 

                    'Product': self.Product_entry.get(), 

                    'Quantity': quantity, 

                    'Price': price, 

                    'Vat rate': vat_rate, 

                    'Netto': netto, 

                    'Tax Amount': tax_amount, 

                    'Invoice Total': total, 

                    'Payment Terms': self.payment_terms_var.get(), 

                    'Invoice Date': dt.date.today() 

                } 

 

                self.manager.save_to_database(data_to_save) 

                messagebox.showinfo("Success", f"Invoice saved as {save_path}") 

                self.root.destroy()

 

        except ValueError: 

            messagebox.showerror('Error', message='Invalid amount or price') 

 

# APPLICATION ENTRY POINT 

InvoiceCreator() 
© Marta-Kowalczyk.com