「毎回Excelを開いて、コピペして、金額を直して…この作業、地味に時間かかるんだよな」

フリーランスとして案件をこなしていると、こんな悩みが出てきますよね。

提案書・見積書の作成って、1件あたり15〜30分はかかります。月に5〜10件こなしていたら、それだけで数時間が消えていく計算です。しかもミス(単価の入力間違い・合計金額のズレ)が起きやすい。

そこで今回は、Pythonで提案書・見積書を自動生成するスクリプトの作り方を解説します。使うのは openpyxlpython-docx の2本柱。テンプレートさえ作ればあとはコマンド1発で完成です。

📌 対象読者:Python基礎がわかる初〜中級者 / フリーランスエンジニア・副業エンジニアの方

なぜPythonで自動化するのか?

freelance invoice automation
freelance invoice automation / Photo by https://kaboompics.com/ via Pexels

手作業の見積書作成には、こんな問題があります。

  • ✅ コピペミス・入力ミスが起きやすい
  • ✅ 案件ごとにフォーマットがバラバラになりがち
  • ✅ 修正が入るたびに全部やり直し
  • ✅ 単純作業に時間をとられて、本業に集中できない

Pythonで自動化すると、データ(品目・単価・数量)を辞書やCSVで管理するだけで、見積書が一発で生成できます。フォーマットは常に統一されるし、修正もデータを直すだけでOK。

つまり「書類を作る時間」を「案件を取る時間」に変換できるわけです。

今回作るものの全体像

今回は以下の構成でスクリプトを作ります。

  • 📄 見積書(Excel形式):openpyxlで生成。品目・単価・数量・小計・消費税・合計を自動計算
  • 📝 提案書(Word形式):python-docxで生成。クライアント名・提案内容・納期をテンプレートから差し込み

まずはライブラリをインストールしておきましょう。

pip install openpyxl python-docx

STEP 1:見積書をExcelで自動生成する

最初に、一番使用頻度が高いExcel形式の見積書から作ります。

ポイントをまとめるとこんな感じです。

  • 案件情報と品目リストを辞書で管理
  • openpyxlでシートにデータを書き込む
  • 合計金額・消費税はPython側で計算して入力
# estimate_generator.py
import openpyxl
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
from datetime import date

def generate_estimate(client_info: dict, items: list, output_path: str):
    """
    見積書をExcelで自動生成する関数

    client_info: クライアント情報の辞書
    items: 品目リスト(品目名・単価・数量を含む辞書のリスト)
    output_path: 出力先ファイルパス
    """

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "見積書"

    # --- カラム幅の設定 ---
    ws.column_dimensions['A'].width = 5
    ws.column_dimensions['B'].width = 30
    ws.column_dimensions['C'].width = 15
    ws.column_dimensions['D'].width = 10
    ws.column_dimensions['E'].width = 18

    # --- スタイル定義 ---
    header_font = Font(bold=True, size=11)
    title_font = Font(bold=True, size=16)
    fill_blue = PatternFill(fill_type="solid", fgColor="4472C4")
    fill_light = PatternFill(fill_type="solid", fgColor="DCE6F1")
    thin_border = Border(
        left=Side(style='thin'), right=Side(style='thin'),
        top=Side(style='thin'), bottom=Side(style='thin')
    )

    # --- タイトル ---
    ws['B1'] = "御見積書"
    ws['B1'].font = title_font
    ws['B1'].alignment = Alignment(horizontal='center')
    ws.merge_cells('B1:E1')

    # --- 発行日・見積番号 ---
    ws['D3'] = "発行日:"
    ws['E3'] = date.today().strftime("%Y年%m月%d日")
    ws['D4'] = "見積番号:"
    ws['E4'] = client_info.get('estimate_no', 'EST-001')

    # --- クライアント情報 ---
    ws['B3'] = f"宛先:{client_info['client_name']} 御中"
    ws['B3'].font = Font(bold=True, size=12)
    ws['B4'] = f"件名:{client_info['project_name']}"
    ws['B5'] = f"有効期限:見積発行より{client_info.get('valid_days', 30)}日間"

    # --- テーブルヘッダー ---
    header_row = 8
    headers = ['No.', '品目・作業内容', '単価(円)', '数量', '小計(円)']
    header_cols = ['A', 'B', 'C', 'D', 'E']

    for col, header in zip(header_cols, headers):
        cell = ws[f"{col}{header_row}"]
        cell.value = header
        cell.font = Font(bold=True, color="FFFFFF")
        cell.fill = fill_blue
        cell.alignment = Alignment(horizontal='center')
        cell.border = thin_border

    # --- 品目の書き込み ---
    subtotal = 0
    for i, item in enumerate(items):
        row = header_row + 1 + i
        unit_price = item['unit_price']
        quantity = item['quantity']
        item_total = unit_price * quantity
        subtotal += item_total

        row_data = [i + 1, item['name'], unit_price, quantity, item_total]
        for col, value in zip(header_cols, row_data):
            cell = ws[f"{col}{row}"]
            cell.value = value
            cell.border = thin_border
            cell.alignment = Alignment(horizontal='right' if col in ['A', 'C', 'D', 'E'] else 'left')

            # 数値はカンマ区切りで表示
            if col in ['C', 'E']:
                cell.number_format = '#,##0'

    # --- 合計・税額 ---
    total_row = header_row + len(items) + 2
    tax_rate = 0.10
    tax = int(subtotal * tax_rate)
    total = subtotal + tax

    summary = [
        ("小計", subtotal),
        ("消費税(10%)", tax),
        ("合計金額(税込)", total),
    ]

    for j, (label, value) in enumerate(summary):
        row = total_row + j
        ws[f"D{row}"] = label
        ws[f"D{row}"].font = Font(bold=True)
        ws[f"D{row}"].border = thin_border
        ws[f"E{row}"] = value
        ws[f"E{row}"].number_format = '#,##0'
        ws[f"E{row}"].border = thin_border
        ws[f"E{row}"].alignment = Alignment(horizontal='right')

        # 合計行は背景色をつけて目立たせる
        if label == "合計金額(税込)":
            ws[f"D{row}"].fill = fill_light
            ws[f"E{row}"].fill = fill_light
            ws[f"E{row}"].font = Font(bold=True, size=12)

    wb.save(output_path)
    print(f"✅ 見積書を生成しました: {output_path}")


# --- 実行サンプル ---
if __name__ == "__main__":

    client_info = {
        "client_name": "株式会社サンプル",
        "project_name": "業務管理システム開発",
        "estimate_no": "EST-2025-001",
        "valid_days": 30,
    }

    items = [
        {"name": "要件定義・設計",          "unit_price": 150000, "quantity": 1},
        {"name": "バックエンド開発(FastAPI)", "unit_price": 80000,  "quantity": 3},
        {"name": "フロントエンド開発",        "unit_price": 70000,  "quantity": 2},
        {"name": "テスト・デバッグ",          "unit_price": 50000,  "quantity": 1},
        {"name": "ドキュメント作成",          "unit_price": 30000,  "quantity": 1},
    ]

    generate_estimate(client_info, items, "見積書_株式会社サンプル.xlsx")

STEP 2:提案書をWordで自動生成する


次はWord形式の提案書です。こちらは python-docx を使います。

ポイントはここです。

  • クライアント名・プロジェクト名・提案内容・納期を引数で受け取る
  • 見出し・本文・箇条書きを構造的に組み立てる
  • 毎回ゼロから書くのではなく、雛形に差し込むイメージで使う
# proposal_generator.py
from docx import Document
from docx.shared import Pt, RGBColor, Cm
from docx.enum.text import WD_ALIGN_PARAGRAPH
from datetime import date

def generate_proposal(client_info: dict, proposal_details: dict, output_path: str):
    """
    提案書をWordで自動生成する関数

    client_info: クライアント情報
    proposal_details: 提案内容(課題・解決策・スケジュール・費用感など)
    output_path: 出力先ファイルパス
    """

    doc = Document()

    # --- ページ余白の設定 ---
    section = doc.sections[0]
    section.top_margin = Cm(2.5)
    section.bottom_margin = Cm(2.5)
    section.left_margin = Cm(2.5)
    section.right_margin = Cm(2.5)

    # --- タイトル ---
    title = doc.add_heading('提案書', level=0)
    title.alignment = WD_ALIGN_PARAGRAPH.CENTER

    # --- 基本情報ブロック ---
    doc.add_paragraph("")
    info_para = doc.add_paragraph()
    info_para.add_run(f"宛先:{client_info['client_name']} 御中\n").bold = True
    info_para.add_run(f"件名:{proposal_details['title']}\n")
    info_para.add_run(f"作成日:{date.today().strftime('%Y年%m月%d日')}\n")
    info_para.add_run(f"作成者:{client_info.get('author_name', '氏名未設定')}")

    doc.add_paragraph("")

    # --- 各セクションを動的に生成 ---
    sections_data = [
        ("1. 背景・課題",      proposal_details.get('background', [])),
        ("2. 提案内容",        proposal_details.get('solution', [])),
        ("3. 期待される効果",  proposal_details.get('benefits', [])),
        ("4. 実施スケジュール", proposal_details.get('schedule', [])),
        ("5. 費用概算",        proposal_details.get('cost_summary', [])),
    ]

    for section_title, bullet_items in sections_data:
        doc.add_heading(section_title, level=2)

        if isinstance(bullet_items, list):
            for item in bullet_items:
                doc.add_paragraph(item, style='List Bullet')
        else:
            doc.add_paragraph(str(bullet_items))

        doc.add_paragraph("")

    # --- フッター的な一言 ---
    doc.add_paragraph("")
    closing = doc.add_paragraph(
        "ご不明な点がございましたら、いつでもご連絡ください。"
        "引き続きよろしくお願いいたします。"
    )
    closing.alignment = WD_ALIGN_PARAGRAPH.LEFT

    doc.save(output_path)
    print(f"✅ 提案書を生成しました: {output_path}")


# --- 実行サンプル ---
if __name__ == "__main__":

    client_info = {
        "client_name": "株式会社サンプル",
        "author_name": "山田 太郎",
    }

    proposal_details = {
        "title": "業務管理システム導入のご提案",
        "background": [
            "現状、案件管理がExcelで行われており、更新の二重入力や集計ミスが発生している",
            "リモートワーク対応のため、リアルタイムな情報共有の仕組みが求められている",
        ],
        "solution": [
            "FastAPIベースのWebアプリケーションとして新規開発",
            "クラウドDBを活用し、どこからでもアクセス可能な環境を構築",
            "既存Excelデータのインポート機能を実装し、移行コストを最小化",
        ],
        "benefits": [
            "入力作業の工数を月20時間以上削減(試算)",
            "リアルタイム共有によるコミュニケーションコストの削減",
            "データの一元管理によるミスの抑制",
        ],
        "schedule": [
            "1〜2週目:要件定義・設計",
            "3〜6週目:開発・実装",
            "7週目:テスト・修正",
            "8週目:納品・運用開始サポート",
        ],
        "cost_summary": [
            "総費用(税込):約660,000円",
            "詳細は別紙見積書をご参照ください",
        ],
    }

    generate_proposal(client_info, proposal_details, "提案書_業務管理システム.docx")

STEP 3:2つをまとめて一括生成するメインスクリプト


最後に、見積書と提案書を1回の実行でまとめて出力するスクリプトを作りましょう。実務ではこれが一番使いやすい形です。

# main.py(一括実行スクリプト)
from estimate_generator import generate_estimate
from proposal_generator import generate_proposal
import os

# --- 案件データをここに入力するだけ! ---
PROJECT = {
    "client_name": "株式会社サンプル",
    "project_name": "業務管理システム開発",
    "estimate_no": "EST-2025-001",
    "valid_days": 30,
    "author_name": "山田 太郎",
}

ITEMS = [
    {"name": "要件定義・設計",          "unit_price": 150000, "quantity": 1},
    {"name": "バックエンド開発(FastAPI)", "unit_price": 80000,  "quantity": 3},
    {"name": "フロントエンド開発",        "unit_price": 70000,  "quantity": 2},
    {"name": "テスト・デバッグ",          "unit_price": 50000,  "quantity": 1},
    {"name": "ドキュメント作成",          "unit_price": 30000,  "quantity": 1},
]

# 出力先フォルダを作成
os.makedirs("output", exist_ok=True)
client = PROJECT['client_name'].replace(' ', '_')

# 見積書・提案書を一括生成
generate_estimate(PROJECT, ITEMS, f"output/見積書_{client}.xlsx")
generate_proposal(
    PROJECT,
    {
        "title": f"{PROJECT['project_name']}のご提案",
        "background": ["現状の課題をここに記入"],
        "solution":   ["提案内容をここに記入"],
        "benefits":   ["期待効果をここに記入"],
        "schedule":   ["スケジュールをここに記入"],
        "cost_summary": ["費用概算をここに記入(別紙見積書参照)"],
    },
    f"output/提案書_{client}.docx"
)

print("\n🎉 書類の生成が完了しました! output/ フォルダを確認してください。")

あとは PROJECTITEMS を書き換えて実行するだけ。案件情報の管理はシンプルな辞書なので、慣れてきたらCSVやJSONから読み込む形に拡張するのもおすすめです。

応用アイデア:もっと便利にするために

基本スクリプトができたら、こんな拡張を試してみてください。

  • 📊 案件一覧をCSVで管理して、一括でまとめて生成する
  • 📧 smtplibと組み合わせてメールに自動添付して送信
  • 🗂 クライアントごとにフォルダを自動作成して整理する
  • 🖨 win32comでExcelをPDF変換(Windowsのみ)してクライアントに送りやすくする

どれも既存のスクリプトに数十行追加するだけで実現できます。少しずつ自分の業務フローに合わせてカスタマイズしていくのが一番です。

まとめ

今回は openpyxlとpython-docxを使って、提案書・見積書を自動生成するPythonスクリプトの作り方を解説しました。

  • openpyxl でExcel見積書を自動生成(スタイル・合計計算込み)
  • python-docx でWord提案書を構造化して生成
  • ✅ メインスクリプトで1コマンド一括出力

書類作成に使っていた時間が浮けば、その分だけ本業の開発・営業・スキルアップに集中できます。フリーランスや副業エンジニアにとって、地味だけど効果絶大な自動化のひとつですよ。

ぜひ自分の案件データで試してみてください。「こういうフォーマットにしたい」「メール送信まで自動化したい」という場合も、ベースのコードがあれば応用はすぐできるはずです 💪

📚 関連商品・おすすめ書籍

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

もしも

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

初心者に定番のPython入門書

Amazonで見る

徹底攻略! 電子工作&プログラミング Arduinoで学ぶ電子工作完全ガイド

もしも

徹底攻略! 電子工作&プログラミング Arduinoで学ぶ電子工作完全ガイド

電子工作とプログラミングを同時に学べる

Amazonで見る

実践Claude Code入門―現場で活用するためのAIコーディングの思考法

もしも

実践Claude Code入門―現場で活用するためのAIコーディングの思考法

AIコーディングの現場活用法を学ぶ一冊

Amazonで見る

※本記事にはアフィリエイトリンクが含まれます。

ABOUT ME
やまちゃん
これまで学生と社会人を合わせて5000人以上にプログラミング学習を指導。 ゼロからイチをわかりやすく解説する専門家として活動しており、本業ではArduinoを用いたIoT開発とロボットプログラミングが専門。 Pythonを用いたアプリ開発、ウェブアプリケーションの開発で業務の効率化をサポートしています。