Skip to content

Instantly share code, notes, and snippets.

@BexTuychiev
Last active March 3, 2026 05:42
Show Gist options
  • Select an option

  • Save BexTuychiev/3bf6d7e13887726ac23dedbb2cf8bb94 to your computer and use it in GitHub Desktop.

Select an option

Save BexTuychiev/3bf6d7e13887726ac23dedbb2cf8bb94 to your computer and use it in GitHub Desktop.
Invoice generator script for OpenClaw skill tutorial
"""Generate a PDF invoice from client details and line items."""
import argparse, json, os
from datetime import date
from reportlab.lib.pagesizes import letter
from reportlab.lib.colors import HexColor
from reportlab.lib.units import inch
from reportlab.platypus import (
SimpleDocTemplate, Table, TableStyle,
Spacer, Paragraph, HRFlowable,
)
from reportlab.lib.styles import ParagraphStyle
DARK = HexColor("#2C3E50")
GRAY = HexColor("#7F8C8D")
WHITE = HexColor("#FFFFFF")
LIGHT_BG = HexColor("#F8F9FA")
def build_invoice(client, items, output_path):
doc = SimpleDocTemplate(
output_path, pagesize=letter,
leftMargin=0.75 * inch, rightMargin=0.75 * inch,
topMargin=0.6 * inch, bottomMargin=0.6 * inch,
)
s = {
"title": ParagraphStyle("t", fontSize=28,
fontName="Helvetica-Bold", textColor=DARK, leading=34),
"label": ParagraphStyle("l", fontSize=10,
fontName="Helvetica-Bold", textColor=GRAY, leading=14),
"value": ParagraphStyle("v", fontSize=10,
fontName="Helvetica", textColor=DARK, leading=14),
"header": ParagraphStyle("h", fontSize=9,
fontName="Helvetica-Bold", textColor=WHITE, leading=12),
"cell": ParagraphStyle("c", fontSize=10,
fontName="Helvetica", textColor=DARK, leading=14),
"bold": ParagraphStyle("b", fontSize=10,
fontName="Helvetica-Bold", textColor=DARK, leading=14),
"total": ParagraphStyle("tot", fontSize=12,
fontName="Helvetica-Bold", textColor=DARK, leading=16),
}
elements = [Paragraph("INVOICE", s["title"]), Spacer(1, 20)]
# Invoice metadata
today = date.today()
inv_num = f"INV-{today:%Y%m}-001"
meta_data = [
[Paragraph("<b>Invoice Number:</b>", s["label"]),
Paragraph(inv_num, s["value"])],
[Paragraph("<b>Date:</b>", s["label"]),
Paragraph(f"{today:%B %d, %Y}", s["value"])],
[Paragraph("<b>Due Date:</b>", s["label"]),
Paragraph("Upon Receipt", s["value"])],
]
meta_table = Table(meta_data, colWidths=[120, 300])
meta_table.setStyle(TableStyle([
("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
("TOPPADDING", (0, 0), (-1, -1), 4),
]))
elements.append(meta_table)
elements.append(Spacer(1, 25))
# From / Bill To
section_header = ParagraphStyle("sh", fontSize=9,
fontName="Helvetica-Bold", textColor=GRAY, leading=12)
from_to_data = [
[Paragraph("FROM", section_header),
Paragraph("BILL TO", section_header)],
[Paragraph(
"<b>Jane Smith</b><br/>"
"Smith Consulting LLC<br/>"
"123 Main St, Suite 400<br/>"
"San Francisco, CA 94105<br/>"
"jane@smithconsulting.com", s["value"]),
Paragraph(
f"<b>{client}</b><br/>"
"billing@acmecorp.com", s["value"])],
]
from_to_table = Table(from_to_data,
colWidths=[doc.width / 2.0] * 2)
from_to_table.setStyle(TableStyle([
("VALIGN", (0, 0), (-1, -1), "TOP"),
("TOPPADDING", (0, 0), (-1, -1), 4),
("BOTTOMPADDING", (0, 0), (-1, -1), 4),
]))
elements.append(from_to_table)
elements.append(Spacer(1, 30))
# Line items table
col_w = [doc.width * w for w in (0.50, 0.15, 0.15, 0.20)]
rows = [[Paragraph(h, s["header"])
for h in ("DESCRIPTION", "HOURS", "RATE", "AMOUNT")]]
subtotal = 0.0
for it in items:
amt = it["hours"] * it["rate"]
subtotal += amt
rows.append([
Paragraph(it["description"], s["cell"]),
Paragraph(str(it["hours"]), s["cell"]),
Paragraph(f"${it['rate']:.2f}", s["cell"]),
Paragraph(f"${amt:.2f}", s["cell"]),
])
tbl = Table(rows, colWidths=col_w)
tbl.setStyle(TableStyle([
("BACKGROUND", (0, 0), (-1, 0), DARK),
("TEXTCOLOR", (0, 0), (-1, 0), WHITE),
("ALIGN", (1, 0), (-1, -1), "CENTER"),
("TOPPADDING", (0, 0), (-1, -1), 10),
("BOTTOMPADDING", (0, 0), (-1, -1), 10),
("BACKGROUND", (0, 1), (-1, -1), LIGHT_BG),
("LINEBELOW", (0, -1), (-1, -1), 0.5, GRAY),
]))
elements.extend([tbl, Spacer(1, 10)])
# Summary rows
tax = subtotal * 0.10
for label, val in [("Subtotal:", subtotal),
("Tax (10%):", tax)]:
elements.append(Table(
[["", "", Paragraph(f"<b>{label}</b>", s["bold"]),
Paragraph(f"${val:.2f}", s["value"])]],
colWidths=col_w,
))
elements.append(
HRFlowable(width="100%", thickness=1, color=DARK))
total = subtotal + tax
elements.append(Table(
[["", "",
Paragraph("<b>TOTAL DUE:</b>", s["total"]),
Paragraph(f"<b>${total:.2f} USD</b>", s["total"])]],
colWidths=col_w,
))
doc.build(elements)
print(f"Created: {output_path}")
if __name__ == "__main__":
p = argparse.ArgumentParser()
p.add_argument("--client", required=True)
p.add_argument("--items", required=True,
help='JSON: [{"description":"...","hours":N,"rate":N}]')
p.add_argument("--output", required=True)
a = p.parse_args()
build_invoice(
a.client, json.loads(a.items),
os.path.expanduser(a.output),
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment