Python Generator Script to Create Dummy PDF Invoices

Need a stack of PDF files to test your code? Use this Invoice Generator Script to create unlimited dummy pdf files.

This Python utility script generates professional invoices in PDF format using the fpdf library. Each invoice includes metadata such as invoice number, date, balance due, and a detailed table of line items. Here’s how you can customize the script to control the number of invoices generated and adjust the invoice numbering.

How to Use and Customize

Controlling the Number of Invoices Created

The script generates a default of 20 invoices. You can control this number by modifying the random.choices call in the following line:

for i, business in enumerate(random.choices(businesses, k=20), start=1):

Steps to Adjust:

Change the value of k in random.choices(businesses, k=20) to your desired number of invoices.

For example, to generate 10 invoices:

for i, business in enumerate(random.choices(businesses, k=10), start=1):

For 50 invoices:

for i, business in enumerate(random.choices(businesses, k=50), start=1):

Save and run the script. The specified number of invoices will be created in the invoices directory.

Changing the Invoice Numbering

Invoice numbers are dynamically generated as random 9-digit numbers in this line:

pdf.cell(50, 8, f"Invoice #: {random.randint(100000000, 999999999)}", ln=True, align='L')

Steps to Customize Invoice Numbers:

  1. Replace the random.randint function with a sequential or fixed pattern.

Example 1: Sequential Invoice Numbers

To use sequential invoice numbers:

pdf.cell(50, 8, f"Invoice #: {index:09d}", ln=True, align='L')
  • This will generate sequential invoice numbers like 000000001, 000000002, etc., based on the index.

Example 2: Prefix with Custom Text

To add a prefix like “INV-” to the invoice numbers:

pdf.cell(50, 8, f"Invoice #: INV-{index:06d}", ln=True, align='L')
  • This will generate invoice numbers like INV-000001, INV-000002, and so on.

Example 3: Fixed Invoice Numbers

For fixed invoice numbers (e.g., for testing purposes):

pdf.cell(50, 8, "Invoice #: INV-2023001", ln=True, align='L')

Additional Customizations:

  1. Modify Line Items for Variety:
  • Add or remove items in the line_items list to tailor the services or products included in the invoices.
  1. Control Large Invoices:
  • By default, 20% of invoices include 25–40 line items. You can adjust this probability or the range:
if random.random() < 0.2:  # Probability of large invoices
    num_items = random.randint(25, 40)
else:
    num_items = random.randint(3, 10)

To make all invoices large, set the condition to always True:

num_items = random.randint(25, 40)

Output Folder:

  • The invoices are saved in the invoices directory. Change this path as needed:

Full Script:

Install fpdf:

pip install fpdf
import os
import random
from fpdf import FPDF
from datetime import datetime, timedelta

# Define the fictitious business names, addresses, emails, and line items
businesses = [
    {"name": "CodeCraft Solutions LLC", "address": "101 Innovation Drive, Suite 203, Tech City, CA 94025", "email": "jdesint@codecraftsolutionsllc.com"},
    {"name": "Digital Horizon Technologies", "address": "501A Skyline Blvd, Suite 1200, New York, NY 10001", "email": "codeteam@digitalhorizontechnical.com"},
    {"name": "PixelPoint Software Inc.", "address": "300 Main Street, Suite 402, Austin, TX 78701", "email": "programming@pixelpointsoftwareinc.com"},
    {"name": "Syntax Savvy Systems", "address": "88 Codex Avenue, Boston, MA 02115", "email": "sales@syntaxsavvysys.com"},
    {"name": "InnovateLogic Software", "address": "52553 Industrial Parkway, Denver, CO 80203", "email": "billing@innovatelogicsoftware.com"},
    {"name": "NeoByte Enterprises", "address": "01 Binary Lane, Silicon Valley, CA 94086-0042", "email": "receivables@neobyteinc.com"},
    {"name": "NextGen Software Solutions", "address": "721 Digital Street, Orlando, FL 32801", "email": "jamessmith@nextgensoftc.com"},
    {"name": "BlueShift Technologies", "address": "900 Orbit Drive, Suite 7, Chicago, IL 60605", "email": "sales@blueshifttechllc.com"},
    {"name": "QuantumByte Systems", "address": "600 Quantum Boulevard, Seattle, WA 98101", "email": "teams@quantumbytesystems.com"},
    {"name": "BrightPath Software Labs", "address": "123 Innovation Crescent, San Francisco, CA 94105", "email": "support@brightpysoftware.com"},
    {"name": "Python Software Labs", "address": "4453 Python Ave., New Castle, DE 19720-1417", "email": "billing@pythonlabscorp.com"},
    {"name": "TechWave Solutions", "address": "456 Technology Park, Suite 300, Boston, MA 02110", "email": "janedoe@ccincsolutions.com"},
    {"name": "CodeCrafters Inc.", "address": "789 Innovation Blvd, San Diego, CA 92101", "email": "systems@codecraftersinc.com"},
    {"name": "NextLevel Software", "address": "101 Progress Ave, Suite 202, Austin, TX 73301", "email": "support@nextlevelsoftllc.com"},
    {"name": "LogicStream Labs", "address": "333 Visionary Lane, Orlando, FL 32819", "email": "accounts@logicstreamlabscode.com"}
]


line_items = [
    "Custom API Development",
    "Backend Development",
    "Backend Optimization",
    "Bug Fixing and QA",
    "Frontend Testing",
    "Backend Testing",
    "Deployment Meeting",
    "Database Schema Update",
    "Django Web Framework Setup",
    "Machine Learning Deployment",
    "Data Parsing and Cleaning Scripts",
    "Python Code Optimization",
    "REST API Implementation",
    "Cloud Deployment Setup",
    "AI Chatbot Development",
    "Web Scraping Scripts",
    "Automation Script Development",
    "Performance Testing and Reports",
    "Algorithm Efficiency Testing",
    "API Integration Services",
    "Cross-Platform Compatibility Testing",
    "Software Architecture Design",
    "Version Control Management",
    "Continuous Integration/Deployment Setup",
    "Database Migration",
    "Containerization with Docker",
    "Kubernetes Cluster Configuration",
    "Server Load Testing",
    "Microservices Architecture Implementation",
    "Codebase Documentation",
    "Code Refactoring for Efficiency",
    "Integration of Third-Party APIs",
    "Custom Plugin Development",
    "Software Security Audit",
    "End-to-End Encryption Implementation",
    "Data Backup and Recovery Automation",
    "UX/UI Prototype Development",
    "Performance Monitoring Setup",
    "Real-Time Data Processing",
    "GraphQL API Implementation",
    "Mobile Application Debugging",
    "Cloud Service Cost Optimization",
    "Codebase Dependency Updates"
]

class CustomInvoicePDF(FPDF):
    def header(self):
        # Add logo
        if os.path.exists("logo.png"):
            self.image("logo.png", x=10, y=8, w=33)  # Place the logo at the top-left corner

        # Move the cursor to position the text below the logo
        self.set_y(45)  # Adjust the vertical position (Y-coordinate)

        # Add company details below the logo with tighter spacing
        self.set_font("Arial", size=12)
        self.cell(0, 6, "Py-Core.com", ln=True, align='L')  # Reduce cell height to 6
        self.cell(0, 6, "4543 Tech Street", ln=True, align='L')
        self.cell(0, 6, "New York, New York 07008", ln=True, align='L')
        self.ln(5)  # Add a small space after the address

def create_invoice(business, index):
    # Randomly decide if this invoice will have a large number of line items
    if random.random() < 0.2:  # 20% chance to have a large invoice
        num_items = random.randint(25, 40)  # Large invoice with 25-40 items
    else:
        num_items = random.randint(3, 10)  # Standard invoice with 3-10 items

    selected_items = random.sample(line_items * (num_items // len(line_items) + 1), k=num_items)
    hourly_rates = [random.randint(50, 85) for _ in range(num_items)]
    hours_worked = [round(random.uniform(0.25, 20), 2) for _ in range(num_items)]

    # Calculate totals
    subtotal = sum(rate * hours for rate, hours in zip(hourly_rates, hours_worked))
    tax_rate = random.uniform(4, 10) / 100
    tax = round(subtotal * tax_rate, 2)
    total = round(subtotal + tax, 2)

    # Ensure the amount paid is always partial (not the full amount)
    amount_paid = random.choice([0, round(total / 4, 2), round(total / 2, 2)])  # Partial payments only
    balance_due = total - amount_paid

    # Create PDF
    pdf = CustomInvoicePDF()
    pdf.add_page()

    # Invoice # stays at the top-right
    pdf.set_font("Arial", style="B", size=14)  # Bold and larger font size
    pdf.set_xy(150, 10)  # Set to the top-right corner
    pdf.cell(50, 8, f"Invoice #: {random.randint(100000000, 999999999)}", ln=True, align='L')

    # Move down 6 lines for the rest of the metadata
    pdf.set_font("Arial", size=12)
    pdf.set_y(10 + 36)  # Start 6 lines below the top
    pdf.set_x(150)  # Reset X position for each line
    pdf.cell(50, 6, f"Date: {datetime.now().strftime('%b %d, %Y')}", ln=True, align='L')
    pdf.set_x(150)
    pdf.cell(50, 6, "Payment Terms: 30 Days", ln=True, align='L')
    pdf.set_x(150)
    pdf.cell(50, 6, f"Due Date: {(datetime.now() + timedelta(days=30)).strftime('%b %d, %Y')}", ln=True, align='L')

    # Balance Due: larger and bold
    pdf.set_x(150)  # Reset X position for Balance Due
    pdf.set_font("Arial", style="B", size=12)  # Bold and larger font size
    pdf.cell(50, 8, f"Balance Due: ${balance_due:.2f}", ln=True, align='L')
    pdf.ln(5)  # Add small spacing after the metadata block

    # Billing info with proper address formatting
    pdf.set_font("Arial", size=12)

    # Split the address into components based on commas
    address_parts = business['address'].split(',')  # Split by all commas
    address_1 = address_parts[0].strip()  # Street address
    address_2 = ', '.join(part.strip() for part in address_parts[1:]) if len(address_parts) > 1 else ""  # City, state, and zip

    pdf.cell(100, 6, f"Bill To: {business['name']}", ln=True)
    pdf.cell(100, 6, address_1, ln=True)  # Street address
    pdf.cell(100, 6, address_2, ln=True)  # City, state, zip
    pdf.cell(100, 6, f"Email: {business['email']}", ln=True)
    pdf.ln(5)  # Add a small gap after the Billing Info section

    # Table header
    pdf.set_font("Arial", style="B", size=10)
    pdf.cell(115, 10, "Item", 1)
    pdf.cell(20, 10, "Quantity", 1)
    pdf.cell(20, 10, "Rate", 1)
    pdf.cell(25, 10, "Amount", 1, ln=True)

    # Table content with automatic page breaks
    pdf.set_font("Arial", size=10)
    for item, rate, hours in zip(selected_items, hourly_rates, hours_worked):
        amount = rate * hours

        # Add a page break if the current position is near the bottom
        if pdf.get_y() > 260:  # Roughly 260 units from the top for an 8.5x11 page
            pdf.add_page()
            # Re-add table header on new page
            pdf.set_font("Arial", style="B", size=10)
            pdf.cell(115, 10, "Item", 1)
            pdf.cell(20, 10, "Quantity", 1)
            pdf.cell(20, 10, "Rate", 1)
            pdf.cell(25, 10, "Amount", 1, ln=True)
            pdf.set_font("Arial", size=10)  # Reset font for table content

        pdf.cell(115, 6, item, 1)
        pdf.cell(20, 6, f"{hours:.2f}", 1)
        pdf.cell(20, 6, f"${rate:.2f}", 1)
        pdf.cell(25, 6, f"${amount:.2f}", 1, ln=True)

    # Totals without cell borders, aligned to the right near "Rate"
    right_align_x = 100  # Adjust this value to align near "Rate"

    pdf.set_x(right_align_x)
    pdf.cell(50, 6, "Subtotal:", 0, align='R')  # Title aligned to the right
    pdf.cell(32, 6, f"${subtotal:.2f}", 0, ln=True, align='R')  # Amount aligned to the right

    pdf.set_x(right_align_x)
    pdf.cell(50, 6, f"Tax ({tax_rate*100:.1f}%):", 0, align='R')
    pdf.cell(32, 6, f"${tax:.2f}", 0, ln=True, align='R')

    pdf.set_x(right_align_x)
    pdf.cell(50, 6, "Total:", 0, align='R')
    pdf.cell(32, 6, f"${total:.2f}", 0, ln=True, align='R')

    pdf.set_x(right_align_x)
    pdf.cell(50, 6, "Amount Paid:", 0, align='R')
    pdf.cell(32, 6, f"${amount_paid:.2f}", 0, ln=True, align='R')

    pdf.set_font("Arial", style="B", size=12)  # Set font to bold
    pdf.set_x(right_align_x)
    pdf.cell(50, 6, "Balance Due:", 0, align='R')
    pdf.cell(32, 6, f"${balance_due:.2f}", 0, ln=True, align='R')

    # Notes
    pdf.set_font("Arial", size=12)
    pdf.ln(10)
    pdf.cell(200, 10, "Notes:", ln=True)
    pdf.multi_cell(0, 10, "Thanks for choosing the Py-Core.com Software Team.\nPayment is due within 30 days from date of invoice. Late payment is subject to fees of 5% per month.")

    return pdf

# Output folder
output_dir = "invoices"
os.makedirs(output_dir, exist_ok=True)

# Generate invoices
for i, business in enumerate(random.choices(businesses, k=20), start=1):
    pdf = create_invoice(business, i)
    pdf.output(os.path.join(output_dir, f"invoice_{i:02d}.pdf"))

print("Invoices generated successfully in the 'invoices' folder.")

Thank you for reading this article. I hope you found it helpful and informative. If you have any questions, or if you would like to suggest new Python code examples or topics for future tutorials, please feel free to reach out. Your feedback and suggestions are always welcome!

Happy coding!
Py-Core.com Python Programming

You can also find this article at Medium.com

Leave a Reply