0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【GAS】スプレッドシートから請求書PDFを一括作成してGmailで送る(インボイス対応の消費税計算つき)

0
Last updated at Posted at 2026-10-07

【GAS】スプレッドシートから請求書PDFを一括作成してGmailで送る(インボイス対応の消費税計算つき)

毎月の請求書づくりを Google Apps Script(GAS)で自動化しました。スプレッドシートに明細を入れてメニューを押すと、次の処理がまとめて終わります。

  • 取引先ごとに請求書シートを組み立てる
  • PDF に変換して Google ドライブの月別フォルダへ保存する
  • 添付付きのメールを Gmail の下書きに作る(またはそのまま送る)

この記事では、実装で引っかかりやすかった次の4点を中心に解説します。

  1. シートを PDF にする方法(getAs では余白やサイズを指定できない)
  2. PDF エクスポートの 429 エラー対策
  3. インボイス制度に沿った消費税の計算と、浮動小数点の落とし穴
  4. Gmail の下書き作成と本文テンプレートの差し込み

全体の構成

シート 役割
設定 自社名、登録番号、振込先、メール文面など
取引先 取引先ID、会社名、担当者、メールアドレス
明細 請求月、取引先ID、品目、数量、単価、税率
発行履歴 発行した請求書番号、金額、PDF の URL

処理の流れは次のとおりです。

明細を請求月で絞り込み → 取引先IDごとにまとめる
  → 作業用シートに請求書を組み立てる → PDF に変換 → ドライブに保存
  → Gmail で下書き作成 → 作業用シートを削除 → 履歴に追記

1. シートを PDF にする

SpreadsheetApp.getActive().getAs('application/pdf') ではスプレッドシート全体が PDF になり、用紙サイズや余白も選べません。特定のシートだけを A4・余白指定で出力したい場合は、エクスポート用の URL に OAuth トークンを付けて取得します。

function exportSheetAsPdf_(ss, sh) {
  const url = 'https://docs.google.com/spreadsheets/d/' + ss.getId() + '/export' +
    '?format=pdf&gid=' + sh.getSheetId() +
    '&size=A4&portrait=true&fitw=true&gridlines=false' +
    '&printtitle=false&sheetnames=false&pagenum=UNDEFINED&fzr=false' +
    '&top_margin=0.6&bottom_margin=0.6&left_margin=0.5&right_margin=0.5';
  const params = {
    headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
    muteHttpExceptions: true,
  };
  return UrlFetchApp.fetch(url, params).getBlob();
}

ポイントは2つです。

  • 書き込んだ直後にエクスポートすると、内容が反映される前の状態で PDF になることがあります。エクスポートの前に SpreadsheetApp.flush() を呼んでください。
  • gid にシートIDを渡すと、そのシートだけを出力できます。請求書は作業用シートに組み立て、PDF にしたら削除しています。
const sh = ss.insertSheet('_請求書作成用_' + invoice.number);
try {
  layoutInvoice_(sh, invoice, settings);
  SpreadsheetApp.flush();
  return exportSheetAsPdf_(ss, sh);
} finally {
  ss.deleteSheet(sh); // 失敗しても作業用シートを残さない
}

2. 連続エクスポートの 429 対策

取引先が多いと、エクスポート URL を短い間隔で叩くことになり、HTTP 429(Too Many Requests)が返ることがあります。1件ごとに1秒待ったうえで、429 と 5xx のときだけ指数バックオフで再試行するようにしました。

for (let attempt = 0; attempt < 5; attempt++) {
  const resp = UrlFetchApp.fetch(url, params);
  const code = resp.getResponseCode();
  if (code === 200) return resp.getBlob();
  if (code !== 429 && code < 500) throw new Error('PDFの作成に失敗しました(HTTP ' + code + ')');
  Utilities.sleep(2000 * Math.pow(2, attempt));
}
throw new Error('PDFの作成に失敗しました(混雑のため)');

GAS の実行時間の上限は6分なので、1回の実行で処理する件数の目安も決めておくと安心です。

3. インボイス制度に沿った消費税の計算

適格請求書(インボイス)では、消費税の端数処理を「1枚の請求書につき、税率ごとに1回」行うことになっています。明細の1行ごとに切り捨てて合計すると、ルールと合わない金額になることがあります。

そこで、税率ごとに税抜金額を合計してから、税率ごとに1回だけ切り捨てています。

const byRate = {};
items.forEach(function (it) {
  const key = String(it.rate);
  byRate[key] = byRate[key] || { rate: it.rate, subtotal: 0 };
  byRate[key].subtotal += it.amount;
});
const taxes = Object.keys(byRate).map(function (k) {
  const b = byRate[k];
  // 浮動小数の誤差を避けるため整数のパーセントで計算する
  return { rate: b.rate, subtotal: b.subtotal, tax: Math.floor(b.subtotal * Math.round(b.rate * 100) / 100) };
});

subtotal * 0.1 をそのまま切り捨てない理由

最初は Math.floor(subtotal * rate) と書いていましたが、浮動小数点の誤差で 1円ずれる可能性があります。たとえば JavaScript では次のようになります。

0.29 * 100            // 28.999999999999996
Math.floor(0.29 * 100) // 28(期待値は 29)

税率をいったん整数のパーセント(10 や 8)に直し、subtotal * 10 / 100 のように整数同士の計算にしてから割ると、この誤差を避けられます。税抜金額 0〜99,999円 のすべてについて Math.floor(x / 10) と一致することを Node.js で確認しました。

軽減税率(8%)の品目には「※」を付け、10%対象と8%対象それぞれの小計と消費税額を請求書に記載しています。

4. Gmail の下書きと本文の差し込み

件名と本文は設定シートで自由に書けるようにし、{会社名} のようなプレースホルダーを置き換えています。

function fillTemplate_(tpl, vars) {
  return String(tpl || '').replace(/\{([^}]+)\}/g, function (all, key) {
    return Object.prototype.hasOwnProperty.call(vars, key) ? vars[key] : all;
  });
}

GmailApp.createDraft(client.email, subject, body, {
  attachments: [file.getBlob()],
  name: settings['メール差出人名'],
});

いきなり送信せず、まず下書きを作るモードを用意しておくと、本番運用で安心して使えます。送信前に人が内容を確認できるからです。

権限について

このスクリプトはスプレッドシート、ドライブ、Gmail、外部 URL への接続(PDF エクスポート用に docs.google.com)の権限を使います。初回実行時に「このアプリは Google で確認されていません」と表示されますが、審査を受けていない自作スクリプトでは必ず表示される画面です。コードの中身を確認したうえで許可してください。

まとめ

  • 特定のシートを A4 の PDF にするには、エクスポート URL と ScriptApp.getOAuthToken() を使う
  • 連続エクスポートは 429 になりうるので、待ち時間と再試行を入れる
  • インボイスの消費税は「税率ごとに合計してから1回だけ端数処理」し、計算は整数で行う
  • メールはまず下書きで作り、人が確認してから送る

今回のコードに、初期設定メニュー、発行履歴、支払期限の自動計算、マニュアルを加えて、すぐ使える形にまとめたものを BOOTH で配布しています。
→ https://smauto.booth.pm/items/8952122

無料版配布中!
→ https://smauto.booth.pm/items/8958489

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?