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