PythonでPDF請求書を自動生成したらミスが激減した話
請求書の手書きでやらかした話
フリーランスや副業で複数のクライアントに月次請求書を発行していると、請求金額の記入ミスや振込先情報の書き忘れが起きます。私自身、Excel手書きの請求書で金額を1桁間違えてクライアントに迷惑をかけたことがあります。
その経験から、PythonとReportLabで請求書生成を自動化しました。クライアント情報と金額をコードに渡すだけでPDFが生成されるので、ミスがほぼゼロになりました。
ReportLabとは
ReportLabはPythonでPDFを生成するためのライブラリです。テキスト、画像、表、グラフを組み合わせたPDFを細かくレイアウト制御しながら生成できます。
pip install reportlab
日本語フォントの設定
ReportLabはデフォルトで日本語が文字化けします。IPAexフォントを使うのが手軽です。
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
# IPAexゴシックを登録(事前にダウンロードが必要)
pdfmetrics.registerFont(TTFont("IPAexGothic", "ipaexg.ttf"))
IPAexフォントはIPAフォントのWebサイトからダウンロードできます。
請求書PDFの生成
from datetime import date
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
pdfmetrics.registerFont(TTFont("IPAexGothic", "ipaexg.ttf"))
def generate_invoice(
output_path: str,
invoice_number: str,
issue_date: date,
client_name: str,
items: list[dict],
tax_rate: float = 0.10,
bank_info: dict | None = None,
) -> None:
"""
PDF請求書を生成する
items: [{"name": "項目名", "quantity": 1, "unit_price": 100000}]
bank_info: {"bank": "〇〇銀行", "branch": "△△支店", "type": "普通", "number": "1234567", "holder": "ナカノ タロウ"}
"""
doc = SimpleDocTemplate(
output_path,
pagesize=A4,
rightMargin=20 * mm,
leftMargin=20 * mm,
topMargin=20 * mm,
bottomMargin=20 * mm,
)
styles = getSampleStyleSheet()
jp_normal = ParagraphStyle(
"JPNormal",
parent=styles["Normal"],
fontName="IPAexGothic",
fontSize=10,
leading=16,
)
jp_title = ParagraphStyle(
"JPTitle",
parent=styles["Title"],
fontName="IPAexGothic",
fontSize=22,
leading=28,
)
jp_small = ParagraphStyle(
"JPSmall",
parent=styles["Normal"],
fontName="IPAexGothic",
fontSize=8,
leading=12,
)
story = []
# タイトル
story.append(Paragraph("請 求 書", jp_title))
story.append(Spacer(1, 6 * mm))
# 宛先・発行情報
header_data = [
[
Paragraph(f"{client_name} 御中", jp_normal),
Paragraph(f"請求書番号: {invoice_number}", jp_normal),
],
[
Paragraph("", jp_normal),
Paragraph(f"発行日: {issue_date.strftime('%Y年%m月%d日')}", jp_normal),
],
]
header_table = Table(header_data, colWidths=[90 * mm, 80 * mm])
story.append(header_table)
story.append(Spacer(1, 8 * mm))
# 明細テーブル
subtotal = sum(item["quantity"] * item["unit_price"] for item in items)
tax = int(subtotal * tax_rate)
total = subtotal + tax
table_data = [
["項目", "数量", "単価(円)", "金額(円)"],
]
for item in items:
amount = item["quantity"] * item["unit_price"]
table_data.append([
item["name"],
str(item["quantity"]),
f"{item['unit_price']:,}",
f"{amount:,}",
])
table_data.append(["", "", "小計", f"{subtotal:,}"])
table_data.append(["", "", f"消費税({int(tax_rate * 100)}%)", f"{tax:,}"])
table_data.append(["", "", "合計(税込)", f"{total:,}"])
col_widths = [90 * mm, 20 * mm, 35 * mm, 35 * mm]
detail_table = Table(table_data, colWidths=col_widths)
detail_table.setStyle(TableStyle([
("FONTNAME", (0, 0), (-1, -1), "IPAexGothic"),
("FONTSIZE", (0, 0), (-1, -1), 9),
("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#2F5496")),
("TEXTCOLOR", (0, 0), (-1, 0), colors.white),
("ALIGN", (1, 0), (-1, -1), "RIGHT"),
("GRID", (0, 0), (-1, -1), 0.5, colors.grey),
("ROWBACKGROUNDS", (0, 1), (-1, -4), [colors.white, colors.HexColor("#F5F5F5")]),
("FONTNAME", (-2, -1), (-1, -1), "IPAexGothic"),
("FONTSIZE", (-2, -1), (-1, -1), 10),
("BACKGROUND", (-2, -1), (-1, -1), colors.HexColor("#E8F0FE")),
]))
story.append(detail_table)
story.append(Spacer(1, 8 * mm))
# 振込先
if bank_info:
story.append(Paragraph("【振込先】", jp_normal))
bank_text = (
f"{bank_info['bank']} {bank_info['branch']} "
f"{bank_info['type']}口座 {bank_info['number']} "
f"口座名義: {bank_info['holder']}"
)
story.append(Paragraph(bank_text, jp_normal))
story.append(Spacer(1, 4 * mm))
story.append(Paragraph("※ お支払い期限は発行月末日とさせていただきます。", jp_small))
doc.build(story)
print(f"請求書を生成しました: {output_path}")
# 使用例
if __name__ == "__main__":
generate_invoice(
output_path="invoice_202603.pdf",
invoice_number="INV-2026-003",
issue_date=date(2026, 3, 1),
client_name="株式会社サンプル",
items=[
{"name": "Webアプリ開発(3月分)", "quantity": 1, "unit_price": 300000},
{"name": "サーバー保守費", "quantity": 1, "unit_price": 20000},
],
bank_info={
"bank": "〇〇銀行",
"branch": "△△支店",
"type": "普通",
"number": "1234567",
"holder": "ナカノ タロウ",
},
)
複数クライアントに一括生成する
CSVからクライアント情報を読み込んで一括生成する場合はこちらです。
import csv
from pathlib import Path
def batch_generate_invoices(csv_path: str, output_dir: str) -> None:
Path(output_dir).mkdir(exist_ok=True)
with open(csv_path, encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
output_path = f"{output_dir}/invoice_{row['invoice_number']}.pdf"
generate_invoice(
output_path=output_path,
invoice_number=row["invoice_number"],
issue_date=date.fromisoformat(row["issue_date"]),
client_name=row["client_name"],
items=[
{
"name": row["item_name"],
"quantity": int(row["quantity"]),
"unit_price": int(row["unit_price"]),
}
],
)
まとめ
ReportLabを使ったPDF請求書自動生成のポイントです。
- 日本語は
TTFontでIPAexフォントを登録してから使う TableとTableStyleで明細テーブルをきれいに整形できるParagraphとSpacerでテキストの余白を制御する- CSV入力と組み合わせることで複数クライアントへの一括生成が可能
手作業の請求書は記入ミスのリスクがあります。自動化することで正確性が上がり、何より請求業務にかける精神的コストが大幅に下がります。フリーランスや副業をしている方にはとくにおすすめの自動化です。