| name | invoice-document-pdf |
| description | Generating invoices, contracts, forms, receipts, and business documents as professional PDFs |
| metadata | {"author":"cosmicstack-labs","version":"1.0.0","category":"pdf-generation","tags":["invoice","pdf-generation","business-documents","contracts","forms"]} |
Invoice & Document PDF Generation
Generate professional business documents — invoices, contracts, receipts, forms — as polished PDFs. This skill covers templates, data-driven generation, batch processing, digital signatures, and workflow automation.
Invoice PDF Structure
A professional invoice follows a standard layout that makes it easy to process, pay, and reconcile.
Standard Invoice Anatomy
┌───────────────────────────────────────────┐
│ [LOGO] INVOICE │
│ Your Company #INV-2025-0042 │
│ 123 Business Rd │
│ City, State ZIP │
├─────────────────┬─────────────────────────┤
│ Bill To: │ Invoice Details: │
│ Client Name │ Date: 2025-01-15 │
│ Client Address │ Due: 2025-02-14 │
│ City, State │ Terms: Net 30 │
│ │ PO #: PO-2025-001 │
├─────────────────┴─────────────────────────┤
│ # │ Description │ Qty │ Rate│ Amt │
│ ───┼───────────────────┼─────┼─────┼─────┤
│ 1 │ Web Development │ 40 │ 150 │$6,000│
│ 2 │ UI/UX Design │ 20 │ 125 │$2,500│
│ 3 │ DevOps Setup │ 8 │ 175 │$1,400│
├────────────────────────┴─────┴─────┴─────┤
│ Subtotal: $9,900 │
│ Tax (8%): $792 │
│ Discount (5%): -$495 │
│ Total: $10,197 │
├───────────────────────────────────────────┤
│ Payment Information: │
│ Bank: First National Bank │
│ Account: XXXX-XXXX-1234 │
│ Routing: 021000021 │
│ PayPal: pay@company.com │
├───────────────────────────────────────────┤
│ Terms & Notes: │
│ Payment due within 30 days. │
│ Late payment subject to 1.5%/mo fee. │
└───────────────────────────────────────────┘
Invoice Data Model
"""Invoice data model with validation."""
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Optional, list
import uuid
@dataclass
class LineItem:
"""A single line item on an invoice."""
description: str
quantity: Decimal
unit_price: Decimal
sku: Optional[str] = None
@property
def amount(self) -> Decimal:
return self.quantity * self.unit_price
@dataclass
class InvoiceData:
"""Complete invoice data structure."""
invoice_number: str
po_number: Optional[str] = None
issue_date: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d"))
due_date: Optional[str] = None
payment_terms: str = "Net 30"
seller_name: str = ""
seller_address: =
seller_city: =
seller_state: =
seller_zip: =
seller_phone: =
seller_email: =
seller_logo_path: [] =
tax_id: [] =
client_name: =
client_address: =
client_city: =
client_state: =
client_zip: =
client_email: [] =
line_items: [LineItem] = field(default_factory=)
tax_rate: Decimal = Decimal()
discount_rate: Decimal = Decimal()
discount_description: =
currency_symbol: =
bank_name: [] =
bank_account: [] =
bank_routing: [] =
payment_instructions: [] =
notes: [] =
terms: [] =
():
.due_date:
due = datetime.now() + timedelta(days=)
.due_date = due.strftime()
.invoice_number:
.invoice_number =
() -> Decimal:
(item.amount item .line_items)
() -> Decimal:
.subtotal * .tax_rate
() -> Decimal:
.subtotal * .discount_rate
() -> Decimal:
.subtotal + .tax_amount - .discount_amount
2. HTML/CSS Print Templates for Invoices
Invoice Print Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Invoice {{ invoice.invoice_number }}</title>
<style>
@page {
size: A4;
margin: 1.5cm 2cm 2cm 2cm;
@bottom-center {
content: "Invoice {{ invoice.invoice_number }} — Page " counter(page);
font-size: 8pt;
color: #999;
font-family: 'Helvetica', 'Arial', sans-serif;
}
}
body {
font-family: 'Helvetica', 'Arial', sans-serif;
font-size: 9.5pt;
color: #333;
line-height: 1.5;
margin: 0;
padding: 0;
}
.invoice-header {
display: flex;
: space-between;
: flex-start;
: ;
: solid ;
: ;
}
{
: ;
}
{
: ;
: ;
: ;
: bold;
}
{
: ;
: ;
: ;
}
{
: right;
}
{
: ;
: ;
: ;
: ;
: uppercase;
}
{
: ;
: ;
: bold;
: ;
}
{
: ;
: normal;
}
{
: flex;
: space-between;
: ;
}
, {
: ;
}
, {
: ;
: ;
: uppercase;
: ;
: ;
: solid ;
: ;
}
, {
: ;
: ;
}
{
: ;
: collapse;
}
{
: ;
: ;
}
{
: ;
: ;
}
{
: right;
: bold;
}
{
: ;
: collapse;
: ;
}
{
: ;
: white;
: ;
: ;
: uppercase;
: ;
: left;
}
{
: right;
}
(),
() {
: right;
}
{
: ;
: solid ;
: ;
}
{
: right;
: bold;
}
(),
() {
: right;
}
(even) {
: ;
}
{
: solid ;
}
{
: flex;
: flex-end;
: ;
}
{
: ;
: collapse;
}
{
: ;
: ;
}
{
: right;
: ;
: ;
}
{
: right;
: bold;
: ;
}
{
: solid ;
: ;
: bold;
: ;
: ;
}
{
: ;
}
{
: ;
: solid ;
: ;
: ;
: avoid;
}
{
: ;
: ;
: uppercase;
: ;
: ;
}
{
: ;
: ;
}
{
: ;
: ;
: ;
: solid ;
: ;
}
{
: ;
: uppercase;
: ;
: ;
: ;
}
{
: ;
: ;
}
{
: absolute;
: ;
: ;
: ;
: ;
}
{% if invoice.seller_logo_path %}
{% endif %}
{{ invoice.seller_name }}
{{ invoice.seller_address }}
{{ invoice.seller_city }}, {{ invoice.seller_state }} {{ invoice.seller_zip }}
{% if invoice.seller_phone %}Phone: {{ invoice.seller_phone }}{% endif %}
{% if invoice.seller_email %}Email: {{ invoice.seller_email }}{% endif %}
{% if invoice.tax_id %}Tax ID: {{ invoice.tax_id }}{% endif %}
Invoice
#{{ invoice.invoice_number }}
Bill To
{{ invoice.client_name }}
{{ invoice.client_address }}
{{ invoice.client_city }}, {{ invoice.client_state }} {{ invoice.client_zip }}
{% if invoice.client_email %}{{ invoice.client_email }}{% endif %}
Invoice Details
Invoice Date{{ invoice.issue_date }}
Due Date{{ invoice.due_date }}
Payment Terms{{ invoice.payment_terms }}
{% if invoice.po_number %}
PO Number{{ invoice.po_number }}
{% endif %}
#
Description
Qty
Rate
Amount
{% for item in invoice.line_items %}
{{ loop.index }}
{{ item.description }}{% if item.sku %}SKU: {{ item.sku }}{% endif %}
{{ item.quantity }}
{{ invoice.currency_symbol }}{{ "{:,.2f}".format(item.unit_price) }}
{{ invoice.currency_symbol }}{{ "{:,.2f}".format(item.amount) }}
{% endfor %}
Subtotal{{ invoice.currency_symbol }}{{ "{:,.2f}".format(invoice.subtotal) }}
{% if invoice.discount_rate > 0 %}
{{ invoice.discount_description }} ({{ "{:.0%}".format(invoice.discount_rate) }})
-{{ invoice.currency_symbol }}{{ "{:,.2f}".format(invoice.discount_amount) }}
{% endif %}
{% if invoice.tax_rate > 0 %}
Tax ({{ "{:.0%}".format(invoice.tax_rate) }}){{ invoice.currency_symbol }}{{ "{:,.2f}".format(invoice.tax_amount) }}
{% endif %}
Total{{ invoice.currency_symbol }}{{ "{:,.2f}".format(invoice.total) }}
Payment Information
{% if invoice.bank_name %}Bank: {{ invoice.bank_name }}{% endif %}
{% if invoice.bank_account %}Account: {{ invoice.bank_account }}{% endif %}
{% if invoice.bank_routing %}Routing: {{ invoice.bank_routing }}{% endif %}
{% if invoice.payment_instructions %}{{ invoice.payment_instructions }}{% endif %}
{% if invoice.notes %}
Notes
{{ invoice.notes }}
{% endif %}
{% if invoice.terms %}
Terms
{{ invoice.terms }}
{% endif %}
3. Python Invoice Generation
ReportLab Invoice Generator
"""Professional invoice generation with ReportLab."""
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm, mm
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
Image, PageBreak, KeepTogether
)
from reportlab.lib.enums import TA_RIGHT, TA_LEFT, TA_CENTER
from decimal import Decimal, ROUND_HALF_UP
from datetime import datetime, timedelta
class InvoiceGenerator:
"""Generate professional invoice PDFs using ReportLab."""
def __init__(self, company_info: dict):
self.company = company_info
self.styles = getSampleStyleSheet()
self._setup_styles()
def _setup_styles(self):
"""Set up custom paragraph styles."""
self.styles.add(ParagraphStyle(
'InvoiceTitle',
fontName='Helvetica-Bold',
fontSize=28,
textColor=colors.HexColor('#1a1a2e'),
alignment=TA_RIGHT,
spaceAfter=4,
))
self.styles.add(ParagraphStyle(
'CompanyName',
fontName='Helvetica-Bold',
fontSize=,
textColor=colors.HexColor(),
spaceAfter=,
))
.styles.add(ParagraphStyle(
,
fontName=,
fontSize=,
textColor=colors.HexColor(),
spaceAfter=,
))
.styles.add(ParagraphStyle(
,
fontName=,
fontSize=,
textColor=colors.HexColor(),
spaceBefore=,
spaceAfter=,
))
() -> Table:
company_cells = [
[Paragraph(.company[], .styles[])],
[Paragraph(.company[], .styles[])],
[Paragraph(
,
.styles[]
)],
]
.company.get():
company_cells.append([Paragraph(, .styles[])])
.company.get():
company_cells.append([Paragraph(, .styles[])])
company_table = Table(company_cells, colWidths=[*cm])
company_table.setStyle(TableStyle([
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
]))
title_cells = [
[Paragraph(, .styles[])],
[Paragraph(, ParagraphStyle(
, parent=.styles[],
fontSize=, textColor=colors.HexColor(),
alignment=TA_RIGHT, fontName=,
))],
]
title_table = Table(title_cells, colWidths=[*cm])
title_table.setStyle(TableStyle([
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
]))
header = Table([[company_table, title_table]], colWidths=[*cm, *cm])
header.setStyle(TableStyle([
(, (, ), (-, -), ),
(, (, ), (-, ), , colors.HexColor()),
(, (, ), (-, ), ),
]))
header
() -> Table:
bill_to = [
[Paragraph(, .styles[])],
[Paragraph(, .styles[])],
[Paragraph(invoice_data[], .styles[])],
[Paragraph(
,
.styles[]
)],
]
invoice_data.get():
bill_to.append([Paragraph(invoice_data[], .styles[])])
bill_table = Table(bill_to, colWidths=[*cm])
bill_table.setStyle(TableStyle([
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (, ), , colors.HexColor()),
]))
details_data = [
[Paragraph(, .styles[]),
Paragraph(, .styles[])],
]
detail_fields = [
(, invoice_data[]),
(, invoice_data[]),
(, invoice_data.get(, )),
]
invoice_data.get():
detail_fields.append((, invoice_data[]))
label, value detail_fields:
details_data.append([
Paragraph(label, .styles[]),
Paragraph(, ParagraphStyle(
, parent=.styles[],
alignment=TA_RIGHT,
)),
])
details_table = Table(details_data, colWidths=[*cm, *cm])
details_table.setStyle(TableStyle([
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, ), , colors.HexColor()),
]))
billing = Table([[bill_table, details_table]], colWidths=[*cm, *cm])
billing.setStyle(TableStyle([
(, (, ), (-, -), ),
]))
billing
() -> Table:
header_row = [, , , , ]
data = [header_row]
idx, item (items, ):
data.append([
(idx),
item[],
(item[]),
,
,
])
col_widths = [*cm, *cm, *cm, *cm, *cm]
table = Table(data, colWidths=col_widths, repeatRows=)
style_cmds = [
(, (, ), (-, ), colors.HexColor()),
(, (, ), (-, ), colors.white),
(, (, ), (-, ), ),
(, (, ), (-, ), ),
(, (, ), (, -), ),
(, (, ), (-, -), ),
(, (, ), (, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), , colors.HexColor()),
(, (, ), (-, ), , colors.HexColor()),
(, (, -), (-, -), , colors.HexColor()),
*[(, (, i), (-, i),
colors.HexColor() i % == colors.white)
i (, (data))],
]
table.setStyle(TableStyle(style_cmds))
table
() -> Table:
data = [
[, ],
]
discount_rate > :
discount_amt = subtotal * discount_rate
data.append([
,
,
])
tax_rate > :
tax_amt = subtotal * tax_rate
data.append([
,
,
])
total = subtotal * ( + tax_rate - discount_rate)
data.append([, ])
table = Table(data, colWidths=[*cm, *cm])
style_cmds = [
(, (, ), (, -), ),
(, (, ), (, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, -), (-, -), , colors.HexColor()),
(, (, -), (-, -), ),
(, (, -), (-, -), ),
(, (, -), (-, -), colors.HexColor()),
]
discount_rate > :
style_cmds += [
(, (, ), (, ), colors.HexColor()),
]
table.setStyle(TableStyle(style_cmds))
table
() -> Table:
cells = [[Paragraph(, .styles[])]]
invoice_data.get():
cells.append([Paragraph(
,
.styles[]
)])
invoice_data.get():
cells.append([Paragraph(
,
.styles[]
)])
invoice_data.get():
cells.append([Paragraph(
,
.styles[]
)])
invoice_data.get():
cells.append([Paragraph(
invoice_data[],
.styles[]
)])
table = Table(cells, colWidths=[*cm])
table.setStyle(TableStyle([
(, (, ), (-, -), colors.HexColor()),
(, (, ), (-, -), , colors.HexColor()),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (-, -), ),
(, (, ), (, ), ),
]))
table
():
doc = SimpleDocTemplate(
output_path,
pagesize=A4,
leftMargin=*cm,
rightMargin=*cm,
topMargin=*cm,
bottomMargin=*cm,
title=,
author=.company[],
)
story = []
story.append(._header_table(invoice_data))
story.append(Spacer(, *cm))
story.append(._billing_section(invoice_data))
story.append(Spacer(, *cm))
item invoice_data.get(, []):
item[] = item[] * item[]
story.append(._items_table(
invoice_data.get(, []),
invoice_data.get(, )
))
story.append(Spacer(, *cm))
story.append(._totals_section(
invoice_data[],
invoice_data.get(, Decimal()),
invoice_data.get(, Decimal()),
invoice_data.get(, ),
))
story.append(Spacer(, *cm))
story.append(._payment_section(invoice_data))
story.append(Spacer(, *cm))
invoice_data.get() invoice_data.get():
notes_cells = []
invoice_data.get():
notes_cells.append([Paragraph(, .styles[])])
notes_cells.append([Paragraph(invoice_data[], .styles[])])
invoice_data.get():
notes_cells.append([Paragraph(, .styles[])])
notes_cells.append([Paragraph(invoice_data[], .styles[])])
notes_table = Table(notes_cells, colWidths=[*cm])
notes_table.setStyle(TableStyle([
(, (, ), (-, ), , colors.HexColor()),
(, (, ), (-, -), ),
]))
story.append(notes_table)
doc.build(story)
()
__name__ == :
generator = InvoiceGenerator({
: ,
: ,
: ,
: ,
: ,
: ,
: ,
})
invoice = {
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: [
{: , : , : Decimal()},
{: , : , : Decimal()},
{: , : , : Decimal()},
{: , : , : Decimal()},
],
: Decimal(),
: Decimal(),
: Decimal(),
: ,
: ,
: ,
: ,
: ,
: ,
: ,
}
generator.generate(invoice, )
FPDF Invoice Generator (Lightweight Alternative)
"""Lightweight invoice generation with FPDF2."""
from fpdf import FPDF
from decimal import Decimal
class InvoicePDF(FPDF):
"""Simple invoice PDF generator using FPDF."""
def header(self):
self.set_font('Helvetica', 'B', 10)
self.set_text_color(26, 26, 46)
self.cell(0, 8, 'INVOICE', align='R', new_x="LMARGIN", new_y="NEXT")
self.line(10, self.get_y(), 200, self.get_y())
self.ln(5)
def footer(self):
self.set_y(-15)
self.set_font('Helvetica', 'I', 8)
self.set_text_color(128, 128, 128)
self.cell(0, 10, f'Page /{{nb}}', align=)
():
.set_font(, , )
.set_text_color(, , )
.cell(, , name, new_x=, new_y=)
.set_font(, , )
.set_text_color(, , )
.cell(, , address, new_x=, new_y=)
.cell(, , , new_x=, new_y=)
.cell(, , , new_x=, new_y=)
.ln()
():
.set_font(, , )
.set_text_color(, , )
.cell(, , , new_x=, new_y=)
.set_font(, , )
.set_text_color(, , )
.cell(, , client_name, new_x=, new_y=)
.set_font(, , )
.cell(, , client_address, new_x=, new_y=)
.cell(, , , new_x=, new_y=)
.ln()
():
.set_font(, , )
.set_text_color(, , )
.cell(, , , new_x=, new_y=)
details = [
(, inv_number),
(, issue_date),
(, due_date),
(, terms),
]
po_number:
details.append((, po_number))
label, value details:
.set_font(, , )
.set_text_color(, , )
.cell(, , label)
.set_font(, , )
.set_text_color(, , )
.cell(, , value, new_x=, new_y=)
.ln()
():
.set_fill_color(, , )
.set_text_color(, , )
.set_font(, , )
col_widths = [, , , , ]
headers = [, , , , ]
i, header (headers):
.cell(col_widths[i], , header, border=, align= i != , fill=)
.ln()
.set_text_color(, , )
fill =
idx, item (items, ):
fill:
.set_fill_color(, , )
:
.set_fill_color(, , )
.set_font(, , )
.cell(col_widths[], , (idx), border=, align=, fill=)
.cell(col_widths[], , item[], border=, align=, fill=)
.cell(col_widths[], , (item[]), border=, align=, fill=)
.cell(col_widths[], , , border=, align=, fill=)
.cell(col_widths[], , , border=, align=, fill=)
.ln()
fill = fill
.line(, .get_y(), , .get_y())
.ln()
():
.set_font(, , )
items = [
(, ),
]
discount_rate > :
discount_amt = subtotal * discount_rate
items.append((, ))
tax_rate > :
tax_amt = subtotal * tax_rate
items.append((, ))
total = subtotal * ( + tax_rate - discount_rate)
items.append((, ))
x_start =
label, value items:
.set_x(x_start)
.set_text_color(, , )
.cell(, , label, align=)
.set_text_color(, , )
.set_font(, label , )
.cell(, , value, align=, new_x=, new_y=)
.ln()
():
.set_fill_color(, , )
.set_font(, , )
.set_text_color(, , )
.cell(, , , border=, fill=, new_x=, new_y=)
.set_font(, , )
.set_text_color(, , )
info = [
(, ),
(, ),
(, ),
]
instructions:
info.append((instructions, ))
label, _ info:
.cell(, , label, border=, new_x=, new_y=)
.ln()
pdf = InvoicePDF()
pdf.alias_nb_pages()
pdf.add_page()
pdf.company_info(, ,
, , , , )
pdf.bill_to(, , , , )
pdf.invoice_details(, , , , )
items = [
{: , : , : ,
: },
{: , : , : , : },
{: , : , : , : },
]
pdf.line_items(items)
pdf.totals(Decimal(), Decimal(), Decimal())
pdf.payment_info(, , ,
)
pdf.output()
()
4. Receipt and Contract Templates
Receipt Template
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
@page { size: A5; margin: 1cm; }
body { font-family: 'Helvetica', sans-serif; font-size: 9pt; color: #333; }
.header { text-align: center; margin-bottom: 0.5cm; }
.header h1 { font-size: 16pt; color: #1a1a2e; margin: 0; }
.header p { color: #999; font-size: 8pt; margin: 2px 0; }
.receipt-info { margin-bottom: 0.5cm; }
.receipt-info table { width: 100%; }
.receipt-info td { : ; : ; }
{ : right; : bold; }
{ : ; : collapse; : ; }
{ : ; : white; : ; : ; : left; }
, () { : right; }
{ : ; : solid ; : ; }
, () { : right; }
{ : right; : ; : bold; : ; }
{ : center; : ; : ; : ; : dashed ; : ; }
Receipt
{{ store_name }} | {{ store_location }}
Receipt #:{{ receipt_number }}
Date:{{ date }}
Cashier:{{ cashier }}
ItemQtyPriceTotal
{% for item in items %}
{{ item.name }}
{{ item.qty }}
\${{ "{:,.2f}".format(item.price) }}
\${{ "{:,.2f}".format(item.total) }}
{% endfor %}
Total: \${{ "{:,.2f}".format(total) }}
Thank you for your purchase!
Contract PDF Generation
"""Generate professional contract PDFs with signature blocks."""
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.lib import colors
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, KeepTogether
)
from reportlab.lib.styles import ParagraphStyle
from datetime import datetime
def generate_contract(output_path: str, contract_data: dict):
"""Generate a PDF contract with signature blocks."""
doc = SimpleDocTemplate(
output_path, pagesize=A4,
leftMargin=2.5*cm, rightMargin=2.5*cm,
topMargin=2.5*cm, bottomMargin=2.5*cm,
title=contract_data.get('title', 'Agreement'),
author=contract_data.get('party_a', ''),
)
styles = {}
styles['title'] = ParagraphStyle(
'ContractTitle', fontName='Helvetica-Bold',
fontSize=18, textColor=colors.HexColor('#1a1a2e'),
spaceAfter=6, alignment=1,
)
styles['subtitle'] = ParagraphStyle(
'ContractSubtitle', fontName='Helvetica',
fontSize=10, textColor=colors.HexColor('#666666'),
spaceAfter=20, alignment=1,
)
styles['h2'] = ParagraphStyle(
, fontName=,
fontSize=, textColor=colors.HexColor(),
spaceBefore=, spaceAfter=,
)
styles[] = ParagraphStyle(
, fontName=,
fontSize=, leading=, textColor=colors.HexColor(),
spaceAfter=, alignment=,
)
styles[] = ParagraphStyle(
, fontName=, fontSize=,
textColor=colors.HexColor(), spaceAfter=,
)
story = []
story.append(Paragraph(contract_data.get(, ), styles[]))
story.append(Paragraph(
,
styles[]
))
story.append(Spacer(, *cm))
story.append(Paragraph(
,
styles[]
))
story.append(Spacer(, *cm))
section contract_data.get(, []):
story.append(Paragraph(section[], styles[]))
paragraph section[]:
story.append(Paragraph(paragraph, styles[]))
story.append(Spacer(, *cm))
story.append(Paragraph(, styles[]))
story.append(Spacer(, *cm))
sig_data = [
[, , , ],
[, , , ],
[, , , ],
]
sig_table = Table(sig_data, colWidths=[*cm, *cm, *cm, *cm])
sig_table.setStyle(TableStyle([
(, (, ), (, ), , colors.HexColor()),
(, (, ), (, ), , colors.HexColor()),
(, (, ), (, ), , colors.HexColor()),
(, (, ), (, ), , colors.HexColor()),
(, (, ), (, ), , colors.HexColor()),
(, (, ), (, ), , colors.HexColor()),
]))
story.append(sig_table)
sig_labels = Table([
[Paragraph(contract_data[], styles[]),
, ,
Paragraph(contract_data[], styles[])],
[Paragraph(, styles[]),
, ,
Paragraph(, styles[])],
[Paragraph(, styles[]),
, ,
Paragraph(, styles[])],
], colWidths=[*cm, *cm, *cm, *cm])
sig_labels.setStyle(TableStyle([
(, (, ), (-, -), ),
(, (, ), (-, -), ),
]))
story.append(sig_labels)
doc.build(story)
()
contract = {
: ,
: ,
: ,
: ,
: [
{
: ,
: [
,
,
],
},
{
: ,
: [
,
,
],
},
{
: ,
: [
,
,
,
],
},
],
}
generate_contract(, contract)
5. QR Codes and Barcodes
"""Add QR codes and barcodes to invoices and documents."""
import qrcode
from io import BytesIO
from reportlab.lib.units import cm
from reportlab.platypus import Image as RLImage
def generate_qr_code(data: str, size: int = 200) -> BytesIO:
"""Generate a QR code image for embedding in PDFs."""
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=10,
border=4,
)
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buffer = BytesIO()
img.save(buffer, format='PNG')
buffer.seek(0)
return buffer
def add_qr_to_document(story, payment_data: dict):
"""Add a payment QR code to an invoice story."""
qr_content = f"""
PAYMENT DETAILS
Invoice: {payment_data.get('invoice_number', '')}
Amount: {payment_data.get('currency', '$')}{payment_data.get('amount', '0')}
Account: {payment_data.get('account', '')}
IFSC:
"""
qr_buffer = generate_qr_code(qr_content.strip(), size=)
qr_img = RLImage(qr_buffer, width=*cm, height=*cm)
qr_table = Table([[qr_img]], colWidths=[*cm])
qr_table.setStyle(TableStyle([
(, (, ), (-, -), ),
(, (, ), (-, -), ),
]))
story.append(qr_table)
():
qrcode
img = qrcode.make(data)
img.save(output_path)
()
save_qr_code(
,
)
6. Batch Invoice Generation
"""Batch invoice generation from spreadsheet data."""
import csv
import json
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
from invoice_generator import InvoiceGenerator
from decimal import Decimal
class BatchInvoiceProcessor:
"""Process invoices in batch from various data sources."""
def __init__(self, generator: InvoiceGenerator, output_dir: str = "invoices"):
self.generator = generator
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
def from_csv(self, csv_path: str, item_csv_dir: str = None):
"""Generate invoices from a CSV file."""
with open(csv_path, 'r') as f:
reader = csv.DictReader(f)
invoices = list(reader)
results = []
for row in invoices:
invoice_data = self._parse_csv_row(row, item_csv_dir)
output_path = str(self.output_dir / f"{invoice_data['invoice_number']}.pdf")
:
.generator.generate(invoice_data, output_path)
results.append((row[], , output_path))
Exception e:
results.append((row[], , (e)))
results
() -> :
invoice = {
: row.get(, ),
: row.get(, ),
: row.get(, ),
: row.get(, ),
: row.get(, ),
: row.get(, ),
: row.get(, ),
: row.get(, ),
: row.get(, ),
: [],
: Decimal(),
: Decimal(row.get(, )),
: row.get(, ),
}
item_dir row.get():
items_path = Path(item_dir) / row[]
items_path.suffix == :
(items_path, ) f:
item_reader = csv.DictReader(f)
item_row item_reader:
qty = Decimal(item_row[])
price = Decimal(item_row[])
item = {
: item_row[],
: qty,
: price,
}
invoice[].append(item)
invoice[] += qty * price
invoice
():
(json_path, ) f:
data = json.load(f)
results = []
invoice_data data.get(, []):
subtotal = Decimal()
item invoice_data.get(, []):
qty = Decimal((item[]))
price = Decimal((item[]))
item[] = qty * price
subtotal += item[]
invoice_data[] = subtotal
output_path = (.output_dir / )
:
.generator.generate(invoice_data, output_path)
results.append((invoice_data[], , output_path))
Exception e:
results.append((invoice_data[], , (e)))
results
():
glob
data_dir = Path(input_dir)
all_results = []
file_path data_dir.glob(file_pattern):
()
file_path.suffix == :
results = .from_csv((file_path))
file_path.suffix == :
results = .from_json((file_path))
:
()
all_results.extend(results)
all_results
() -> :
results = []
ThreadPoolExecutor(max_workers=max_workers) executor:
future_to_inv = {}
inv_data invoices_data:
output_path = (.output_dir / )
future = executor.submit(
.generator.generate, inv_data, output_path
)
future_to_inv[future] = (inv_data[], output_path)
future as_completed(future_to_inv):
inv_number, output_path = future_to_inv[future]
:
future.result()
results.append((inv_number, , output_path))
Exception e:
results.append((inv_number, , (e)))
results
gen = InvoiceGenerator({: , ...})
processor = BatchInvoiceProcessor(gen, )
results = processor.from_directory(, )
success = ( _, ok, _ results ok)
failed = ( _, ok, _ results ok)
()
7. PDF/A Compliance for Archiving
"""Create PDF/A-compliant documents for long-term archiving."""
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
def create_pdfa_compliant(output_path: str, content: str):
"""Create a PDF/A-1b compliant document."""
c = canvas.Canvas(output_path, pagesize=A4)
c.setTitle("Archived Document")
c.setAuthor("Cosmic Stack Labs")
c.setSubject("PDF/A Compliant Document")
c.setFont('Helvetica', 11)
c.setFillColorRGB(0, 0, 0)
c.drawString(2*cm, A4[1] - 2*cm, content)
c.showPage()
c.save()
print(f"PDF/A-compliant document: {output_path}")
def ():
PyPDF2 PdfReader, PdfWriter
reader = PdfReader(input_pdf)
writer = PdfWriter()
page reader.pages:
writer.add_page(page)
metadata = reader.metadata {}
writer.add_metadata({
: metadata.get(, ),
: metadata.get(, ),
: ,
: ,
})
(output_pdf, ) f:
writer.write(f)
()
8. Email + PDF Workflow Automation
"""Automated email + PDF invoice delivery system."""
import smtplib
import ssl
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders
from pathlib import Path
import csv
import json
class InvoiceDeliverySystem:
"""Send generated invoices via email automatically."""
def __init__(self, smtp_config: dict):
self.smtp_host = smtp_config['host']
self.smtp_port = smtp_config['port']
self.smtp_user = smtp_config['user']
self.smtp_password = smtp_config['password']
self.from_address = smtp_config['from_address']
self.from_name = smtp_config.get('from_name', 'Billing Department')
def send_invoice(self, recipient_email: str, recipient_name: str,
invoice_path: str, invoice_number: str,
amount: str, due_date: str):
"""Send an invoice email with PDF attachment."""
msg = MIMEMultipart()
msg[] =
msg[] = recipient_email
msg[] =
html =
msg.attach(MIMEText(html, ))
(invoice_path, ) f:
attachment = MIMEBase(, )
attachment.set_payload(f.read())
encoders.encode_base64(attachment)
attachment.add_header(
,
)
msg.attach(attachment)
context = ssl.create_default_context()
smtplib.SMTP_SSL(.smtp_host, .smtp_port, context=context) server:
server.login(.smtp_user, .smtp_password)
server.sendmail(.from_address, recipient_email, msg.as_string())
()
():
(recipients_csv, ) f:
reader = csv.DictReader(f)
row reader:
invoice_path = Path(invoice_dir) /
invoice_path.exists():
.send_invoice(
recipient_email=row[],
recipient_name=row[],
invoice_path=(invoice_path),
invoice_number=row[],
amount=row[],
due_date=row[],
)
:
()
():
msg = MIMEMultipart()
msg[] =
msg[] = recipient_email
msg[] =
html =
msg.attach(MIMEText(html, ))
context = ssl.create_default_context()
smtplib.SMTP_SSL(.smtp_host, .smtp_port, context=context) server:
server.login(.smtp_user, .smtp_password)
server.sendmail(.from_address, recipient_email, msg.as_string())
()
Scoring Rubric
| Criteria | 1 (Basic) | 2 (Functional) | 3 (Proficient) | 4 (Advanced) | 5 (Expert) |
|---|
| Layout | Plain text | Basic formatting | Professional layout | Branded template | Multi-page with design |
| Data Handling | Manual entry | Single file input | CSV/JSON import | Multiple sources | Real-time API integration |
| Line Items | Fixed list | Simple table | Formatted with styles | Conditional formatting | Dynamic grouping/subtotals |
| Automation | Manual generation | Shell script | Batch processing | Parallel generation | Full pipeline with email |
| Compliance | None | Basic info included | Tax calculations | PDF/A, digital signatures | Regulatory compliance |
| Delivery | Manual send | Email attachment | Batch email | Scheduled delivery | Automated reminders |
Common Mistakes
- Incorrect tax calculations: Tax rounding errors add up in batch processing. Use
Decimal for all monetary calculations, never floats.
- Missing payment information: An invoice without clear payment instructions will delay payment. Always include bank details and payment links.
- No invoice numbering system: Sequential, non-repeating invoice numbers are required for accounting. Use a prefix + year + sequence pattern.
- Line items without descriptions: Vague descriptions like "Services rendered" cause disputes. Be specific about scope, quantity, and rate.
- Ignoring PDF file size: Large invoices with embedded high-res images can be 50MB+. Resize logos to 150 DPI and compress images.
- Not testing different page sizes: An invoice designed for A4 may break on Letter. Test both if your clients are international.
- No backup of generated invoices: Regenerating invoices from data can fail if source data changes. Always archive the final PDF.
- Forgetting decimal precision: Monetary values should always use 2 decimal places. Use
Decimal('10.00') not 10.0.
- No PDF metadata: Invoices without title, author, or subject are hard to search in document management systems.
- Mishandling negative amounts: Credits, discounts, and adjustments should be clearly marked and calculated correctly in the total.