【VBA】Excelで請求書を自動作成してPDF保存する方法(コピペOK)

VBA

完成イメージ(Before / After)

Before(手作業で転記)

Before(実行前)のExcel画面
A B C D E
1 請求番号 取引先名 件名 金額 状態
2 INV-001 株式会社ABC Webサイト制作 500000 手動転記中…
3 INV-002 株式会社DEF システム保守 300000 まだ
4 INV-003 株式会社GHI コンサル 200000 まだ

1件ずつコピペ。20社で2時間。ミスで再発行のリスクあり。

After(マクロで一括PDF保存)

After(実行後)のExcel画面
A B C D E
1 請求番号 取引先名 件名 金額 結果
2 INV-001 株式会社ABC Webサイト制作 500000 PDF保存済み
3 INV-002 株式会社DEF システム保守 300000 PDF保存済み
4 INV-003 株式会社GHI コンサル 200000 PDF保存済み

ボタン1つで全件PDF保存。5分で完了。転記ミスゼロ。

一覧シート(入力するデータ):

A B C D E
1 請求番号 取引先名 件名 金額 請求日
2 INV-001 株式会社ABC Webサイト制作 500000 2026/03/31
3 INV-002 株式会社DEF システム保守 300000 2026/03/31
4 INV-003 株式会社GHI コンサルティング 200000 2026/03/31

出力されるPDFファイル:


請求書PDF\
  ├── 請求書_株式会社ABC_20260331.pdf
  ├── 請求書_株式会社DEF_20260331.pdf
  └── 請求書_株式会社GHI_20260331.pdf

毎月20社分の請求書を手作業で作っていた時期がある。一覧表を見ながらテンプレートに1件ずつ転記して、PDF保存して、また次の取引先。金額のコピペミスで再発行になったことが2回ある。正直、月末の請求書作業が怖かった。

VBAで自動化してからはミスゼロになった。作業時間は2時間→5分。一覧シートにデータを入力してマクロを実行するだけで、PDFが全件できあがる。月末の請求書作業が怖くなくなった。

同じように毎月の請求書で消耗している人に、この一括処理を体験してほしい。まずは最小版で1件だけ転記して、動くことを確認するところから始めよう。

一覧表 → テンプレート転記 → PDF保存。この流れをVBAで自動化する。

事前準備

シート構成を用意する

このマクロは2つのシートを使う。

「一覧」シート — 請求データを入力する場所

内容
A列 請求番号 INV-001
B列 取引先名 株式会社ABC
C列 件名 Webサイト制作
D列 金額 500000
E列 請求日 2026/03/31
  • 1行目はヘッダー。データは2行目から入力する
  • A列が空の行が最終行として判定される

「請求書」シート — テンプレート(転記先)

セル 内容
C3 取引先名(宛名)
C5 件名
C8 請求番号
E3 請求日
D15 金額

テンプレートの作り方は本記事の後半で解説する。

バックアップを取る

テンプレートシートにデータを上書きする処理のため、実行前にファイルのコピーを取っておくこと。テスト用のダミーデータで動作確認してから本番データで実行することを強く推奨する。

Excelをマクロ有効ブック(.xlsm)で保存する

拡張子が .xlsx のままだとマクロは保存できない。

  1. 「ファイル」→「名前を付けて保存」
  2. ファイルの種類を「Excelマクロ有効ブック (*.xlsm)」に変更
  3. 保存

手順(コピペ → 実行まで約5分)

VBE(コードを書く画面)を開く

  1. Excelで Alt + F11 を押す
  2. VBE(Visual Basic Editor)が開く

標準モジュールを挿入する

  1. VBEのメニュー →「挿入」→「標準モジュール」
  2. 白い画面(コードウィンドウ)が表示される

コードを貼り付けて実行する

  1. コードウィンドウに、下のコードをそのままコピペする
  2. コード内の書き換えポイント(★マーク)を自分の環境に合わせて変更する
  3. Alt + F8 → マクロ名を選んで「実行」

コード(最小版)– 一覧から請求書テンプレートに1件転記

まず1件だけ転記して動作確認する。ループもPDF保存もない最小構成。

セルの転記の基本はセルの転記を自動化する方法を参照。


Sub 請求書を作成する()
    Dim wsList As Worksheet
    Dim wsTemplate As Worksheet
    Dim lastRow As Long
    Dim i As Long

    Set wsList = Worksheets("一覧")
    Set wsTemplate = Worksheets("請求書")

    ' ★ 最終行を取得(A列で判定)
    lastRow = wsList.Cells(wsList.Rows.Count, 1).End(xlUp).Row

    If lastRow < 2 Then
        MsgBox "一覧にデータがありません。", vbExclamation
        Exit Sub
    End If

    ' 1件目のデータを転記(動作確認用)
    With wsTemplate
        .Range("C3").Value = wsList.Cells(2, 2).Value   ' 取引先名
        .Range("C5").Value = wsList.Cells(2, 3).Value   ' 件名
        .Range("E3").Value = wsList.Cells(2, 5).Value   ' 請求日
        .Range("D15").Value = wsList.Cells(2, 4).Value  ' 金額
        .Range("C8").Value = wsList.Cells(2, 1).Value   ' 請求番号
    End With

    wsTemplate.Activate
    MsgBox "請求書を作成しました。"
End Sub

書き換えポイント

# 書き換え箇所 初期値 説明
1 Worksheets("一覧") 一覧 一覧データのシート名
2 Worksheets("請求書") 請求書 テンプレートのシート名
3 .Range("C3") C3, C5, E3, D15, C8 テンプレート側の転記先セル
4 wsList.Cells(2, 2) 列2,3,5,4,1 一覧側の列番号(B=2, C=3 …)

コード解説

最小版のポイントは With wsTemplate ... End With の部分だ。With を使うと、同じオブジェクト(ここでは請求書シート)への操作をまとめて書けるので、コードが短くなり読みやすくなる。なぜ Cells(2, 2) のように列番号で指定するかというと、一覧シートの列構成(A列=請求番号、B列=取引先名…)に合わせて数値で対応させるためだ。列を追加・変更した場合はこの番号を修正すること。

lastRow の取得は A列で判定しているため、A列が空のままB列以降にデータがあると「データなし」と誤判定される。一覧の主キー(ここでは請求番号)が入る列で判定するのがコツだ。最終行の取得の詳細も確認しておくとよい。

動作確認

  1. 「一覧」シートの2行目にテスト用データを1件入力する
  2. マクロを実行する
  3. 「請求書」シートに切り替わり、データが転記されていることを確認する

最小版で転記が確認できたら、次の実務版に進む。

コード(実務版)– 全件ループ+PDF保存+確認ダイアログ+進捗表示

自分はこの方法で毎月20社分の請求書を処理している。2時間かかっていた作業が5分で終わるようになった。

一覧の全件をループしてテンプレートに転記し、1件ずつPDF保存する。PDF保存の基本はExcelファイルをPDFに一括変換する方法を参照。


Sub 請求書を一括作成してPDF保存()
    Dim wsList As Worksheet
    Dim wsTemplate As Worksheet
    Dim lastRow As Long
    Dim i As Long
    Dim savePath As String
    Dim fileName As String
    Dim cnt As Long
    Dim clientName As String

    Set wsList = Worksheets("一覧")
    Set wsTemplate = Worksheets("請求書")

    ' ★ 最終行を取得(A列で判定)
    lastRow = wsList.Cells(wsList.Rows.Count, 1).End(xlUp).Row

    If lastRow < 2 Then
        MsgBox "一覧にデータがありません。", vbExclamation
        Exit Sub
    End If

    ' ★ 保存先フォルダ(ブックと同じ場所に「請求書PDF」フォルダ)
    savePath = ThisWorkbook.Path & "\請求書PDF\"
    If Dir(savePath, vbDirectory) = "" Then
        MkDir savePath
    End If

    ' 確認ダイアログ(件数+保存先を表示)
    If MsgBox((lastRow - 1) & " 件の請求書を作成します。" & vbCrLf & _
              "保存先: " & savePath & vbCrLf & vbCrLf & _
              "実行しますか?", vbYesNo + vbQuestion) = vbNo Then
        Exit Sub
    End If

    Application.ScreenUpdating = False

    For i = 2 To lastRow
        ' ステータスバーに進捗表示
        Application.StatusBar = "請求書作成中... " & (i - 1) & "/" & (lastRow - 1)

        ' テンプレートに転記
        With wsTemplate
            .Range("C3").Value = wsList.Cells(i, 2).Value   ' 取引先名
            .Range("C5").Value = wsList.Cells(i, 3).Value   ' 件名
            .Range("E3").Value = wsList.Cells(i, 5).Value   ' 請求日
            .Range("D15").Value = wsList.Cells(i, 4).Value  ' 金額
            .Range("C8").Value = wsList.Cells(i, 1).Value   ' 請求番号
        End With

        ' ファイル名の禁止文字を置換
        clientName = Replace(wsList.Cells(i, 2).Value, "/", "/")
        clientName = Replace(clientName, "\", "\")
        clientName = Replace(clientName, ":", ":")
        clientName = Replace(clientName, "*", "*")
        clientName = Replace(clientName, "?", "?")
        clientName = Replace(clientName, """", """)
        clientName = Replace(clientName, "<", "<")
        clientName = Replace(clientName, ">", ">")
        clientName = Replace(clientName, "|", "|")

        ' ★ PDFファイル名: 請求書_取引先名_日付.pdf
        fileName = "請求書_" & clientName & "_" & _
                   Format(wsList.Cells(i, 5).Value, "yyyymmdd") & ".pdf"

        ' PDF保存
        wsTemplate.ExportAsFixedFormat _
            Type:=xlTypePDF, _
            Filename:=savePath & fileName, _
            Quality:=xlQualityStandard

        cnt = cnt + 1
    Next i

    ' テンプレートをクリア(前回データの残留防止)
    With wsTemplate
        .Range("C3").ClearContents
        .Range("C5").ClearContents
        .Range("E3").ClearContents
        .Range("D15").ClearContents
        .Range("C8").ClearContents
    End With

    Application.StatusBar = False
    Application.ScreenUpdating = True

    MsgBox cnt & " 件の請求書PDFを保存しました。" & vbCrLf & _
           "保存先: " & savePath, vbInformation
End Sub

書き換えポイント

# 書き換え箇所 初期値 説明
1 "\請求書PDF\" 請求書PDF PDF保存先フォルダ名
2 .Range("C3") C3, C5, E3, D15, C8 テンプレート側の転記先セル
3 wsList.Cells(i, 2) 列2,3,5,4,1 一覧側の列番号
4 "請求書_" 請求書_ PDFファイル名のプレフィックス

実務版で追加した機能

機能 説明 参考記事
確認ダイアログ 件数+保存先を表示してYes/Noで確認
進捗表示 ステータスバーに「5/20」形式で進捗を表示 進捗表示の方法
PDF一括保存 ExportAsFixedFormatで1件ずつPDF化 PDF一括変換
フォルダ自動作成 MkDirで保存先フォルダを自動作成
禁止文字置換 ファイル名に使えない文字を全角に変換
テンプレートクリア ループ完了後にClearContentsで前回データを消去

実務版で特に重要なのは禁止文字の置換処理だ。Windowsのファイル名には \ / : * ? " < > | の9文字が使えない。取引先名にこれらが含まれていると ExportAsFixedFormat で実行時エラーになる。実務版ではすべて全角に変換しているので安心だが、仕組みを知っておくとトラブル時の原因特定が早くなる。文字列の置換も参考にしてほしい。

また、ループの最後に ClearContents でテンプレートをクリアしている理由は、次回実行時に前回のデータが残ったまま転記されるのを防ぐためだ。特に一覧のデータ件数が前回より少ない場合、最後の1件に前回のデータが混入するリスクがある。

テンプレートの作り方

「請求書」シートのレイアウト例を示す。実際のデザインは自由に変更してよい。重要なのは転記先のセル位置を合わせること。

レイアウト例


行1  : [空白]
行2  : [空白]        [空白]        請 求 書
行3  : [空白]  C3:取引先名 御中           E3:請求日
行4  : [空白]
行5  : [空白]  C5:件名
行6  : [空白]
行7  : [空白]  下記の通りご請求申し上げます。
行8  : [空白]  C8:請求番号
行9  : [空白]
行10 : [空白]  ─────────────────
行11 : [空白]  項目           数量    単価      金額
行12 : [空白]  ─────────────────
行13 : [空白]  (明細行)
行14 : [空白]  ─────────────────
行15 : [空白]  [空白]         [空白]  小計    D15:金額
行16 : [空白]  [空白]         [空白]  消費税  (数式: =D15*0.1)
行17 : [空白]  [空白]         [空白]  合計    (数式: =D15+D16)
行18 : [空白]
行19 : [空白]  振込先: ○○銀行 ○○支店 普通 1234567
行20 : [空白]  支払期限: 翌月末日

ポイント

  • 消費税(D16)と合計(D17)はExcelの数式で計算する。VBA側で転記するのは小計(D15)だけでよい
  • テンプレートの書式(フォント、罫線、列幅)は事前に整えておく
  • 印刷範囲を設定しておくとPDFのレイアウトが安定する(「ページレイアウト」→「印刷範囲の設定」)
  • 金額セル(D15)の表示形式は #,##0 に設定しておく。書式設定の詳細はセルの書式を一括変更する方法を参照

よくある落とし穴5選

# 症状 原因 対策
1 「パスが見つかりません」エラー PDF保存先フォルダが存在しない MkDir で自動作成する。実務版コードでは Dir(savePath, vbDirectory) で存在チェック後に作成している
2 PDF保存時に「実行時エラー 1004」 取引先名に / \ : などファイル名に使えない文字が含まれている Replace 関数で禁止文字を全角に置換する。実務版コードでは9種類の禁止文字を置換済み
3 金額が「500000」と表示される(カンマなし) テンプレートのセル書式が「標準」になっている テンプレートのD15セルの表示形式を #,##0 に設定する。または wsTemplate.Range("D15").NumberFormat = "#,##0" をコードに追加
4 2回目の実行でテンプレートに前回データが残る ループ終了後にClearContentsしていない 実務版コードではループ完了後に全転記セルをClearContentsしている。最小版で試す場合も手動でクリアすること
5 一覧の列を追加したらデータがズレた Cells(i, 2) のように列番号をハードコーディングしている 列番号を Const で定数化する(例: Const COL_CLIENT As Long = 2)。列を変更したら定数だけ直せばよい

取引先名に「/」が入っていてPDF保存でエラーになったことがある。「株式会社A/B」のような社名で、ファイル名に使えない文字だと気づかずに30分溶かした。実務版コードでは最初からReplace処理を入れているので同じ失敗は起きない。

VBAで請求書テンプレートに値が入らないときの対処法

「マクロを実行したのにテンプレートのセルが空のまま」という場合、原因はテンプレート側の転記先セル番地と、コード内の .Range("C3") 等の指定がずれていることが多い。テンプレートのレイアウトを変更した後にコードのセル番地を修正し忘れるパターンだ。対処法はテンプレートシートで実際に転記先セルをクリックしてセル番地を確認し、コード内の指定と一致させること。もう1つの原因は、一覧シートの列番号が合っていないケース。A列=1、B列=2…の対応を間違えていないか確認すること。

VBAの差し込み印刷がうまくいかないときの対処法

「ループで複数件処理したのに、最後の1件のデータだけが全件分のPDFに出力されてしまう」という場合、原因はテンプレートへの転記とPDF保存の順番が逆になっているか、PDF保存のコードがループの外に出てしまっていることが多い。対処法は「転記→PDF保存」を1セットとしてループ内に収めること。自分も最初にループの外でExportAsFixedFormatを呼んでしまい、20社分のPDFがすべて最後の取引先名で出力されて焦った経験がある。転記とPDF保存はループ内でペアにするのが鉄則だ。

FAQ

Q1. 請求書に明細行(複数行の品目)を入れたい

一覧シートの構成を変更する。1つの請求番号に対して複数行を持たせ、ループ処理で請求番号が変わるタイミングでPDF保存する。本記事の構成は「1行=1請求書」の最もシンプルなパターン。明細対応は別記事で解説予定。

Q2. 既存のPDFが上書きされるのを防ぎたい

Dir() で同名ファイルの存在をチェックしてスキップする。


If Dir(savePath & fileName) <> "" Then
    ' 既に存在する場合はスキップ
    GoTo NextRecord
End If

Q3. 請求日を今日の日付で自動設定したい

一覧シートのE列に Date 関数の値を入れるか、コード内で直接設定する。


wsTemplate.Range("E3").Value = Date

Q4. 消費税を自動計算してテンプレートに反映したい

テンプレートのD16セルにExcelの数式 =D15*0.1 を入れておく。VBAで小計(D15)を転記すると消費税は数式で自動計算される。コード側で計算する必要はない。

Q5. ボタンからマクロを実行したい

マクロをボタン1つで実行する方法を参照。シート上にボタンを設置して OnAction にマクロ名「請求書を一括作成してPDF保存」を割り当てる。経理以外の人にも使ってもらう場合はボタン化が便利。

まとめ

この記事では、VBAで一覧表から請求書テンプレートにデータを自動転記し、PDFファイルとして一括保存する方法を解説した。

  • 最小版: 1件だけ転記して動作確認
  • 実務版: 全件ループ + PDF保存 + 確認ダイアログ + 進捗表示

テスト用のダミーデータで動作確認してから、本番データで実行すること。

請求書の自動作成は、VBAの自動化で最も「投資対効果が高い」テーマの1つだ。毎月繰り返す作業だからこそ、一度マクロを組めば年間で何十時間もの工数削減になる。まずは最小版で1件だけ転記して動作を確認し、テンプレートのセル位置が合っていることを確かめてから実務版に進むのが安全な手順だ。ブックの保存と組み合わせれば、PDF出力後にブックを自動保存して閉じるところまで一気に自動化できる。

関連記事

次にやりたくなること

  • 請求番号を連番で自動採番したい → 連番で請求番号を自動採番する方法で、INV-001, INV-002… を自動生成できる
  • 支払期限を営業日で計算したい → 営業日で支払期限を計算する方法で、土日祝を除いた期限日を自動設定できる

コメント

タイトルとURLをコピーしました