GASで請求書を自動生成、過去データから一気通貫で発行する仕組み

「コピペ、なくなった。」案件データから請求書PDFを自動生成しGmailで送信する仕組みのアイキャッチ画像

結論:「案件一覧(同期)」のデータから、関数を実行するだけで請求書PDFを自動生成し、Gmailで送信するところまで作りました。案件名や金額をコピペする作業が一切なくなりました。

この記事でわかること

  • 請求書作成にかかっていた手間
  • 既存データから一気通貫で生成する設計
  • GASでのPDF生成・送信の実装コード
  • つまずいたポイント

前回の記事で、クライアント対応履歴を自動で残せるようになりました。今回はこれまで貯めてきた「案件一覧(同期)」のデータを使って、請求書作成まで自動化します。

請求書作成にかかっていた手間

案件名や金額はすでにNotion・スプレッドシートに入力済みなのに、請求書を作るときだけ改めて別のテンプレートにコピペし直していました。二重入力をなくす、と決めていたはずなのに、請求書だけ取り残されていた形です。

既存データから一気通貫で生成する設計

ゼロから請求書を作るのではなく、これまでの記事で作ってきた「案件一覧(同期)」シートを唯一のデータ源にします。案件名を指定するだけで、金額などの情報を自動で拾ってPDF化する設計にしました。

GASでのPDF生成・送信の実装

const TEMPLATE_SHEET_ID = 'ここに請求書テンプレートのスプレッドシートIDを入れる';
const INVOICE_FOLDER_ID = 'ここに請求書PDFを保存するGoogleドライブのフォルダIDを入れる';

function generateInvoice(projectName) {
  const project = getProjectByName_(projectName);
  if (!project) throw new Error('該当する案件が見つかりません: ' + projectName);

  const copy = DriveApp.getFileById(TEMPLATE_SHEET_ID)
    .makeCopy(`請求書_${project.name}`, DriveApp.getFolderById(INVOICE_FOLDER_ID));
  const ss = SpreadsheetApp.openById(copy.getId());
  const sheet = ss.getSheets()[0];

  sheet.getRange('B2').setValue(project.name);
  sheet.getRange('B3').setValue(project.amount);
  sheet.getRange('B4').setValue(new Date());
  SpreadsheetApp.flush();

  const pdfBlob = exportSheetAsPdf_(ss.getId(), sheet.getSheetId())
    .setName(`請求書_${project.name}.pdf`);
  DriveApp.getFolderById(INVOICE_FOLDER_ID).createFile(pdfBlob);

  GmailApp.sendEmail(
    '自分のメールアドレス',
    `請求書を作成しました: ${project.name}`,
    'PDFを添付しています。',
    { attachments: [pdfBlob] }
  );

  copy.setTrashed(true);
}

function exportSheetAsPdf_(spreadsheetId, sheetId) {
  const url = `https://docs.google.com/spreadsheets/d/${spreadsheetId}/export?format=pdf&gid=${sheetId}&size=A4&portrait=true&fitw=true`;
  const token = ScriptApp.getOAuthToken();
  const response = UrlFetchApp.fetch(url, { headers: { Authorization: `Bearer ${token}` } });
  return response.getBlob();
}

function getProjectByName_(name) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('案件一覧(同期)');
  const data = sheet.getDataRange().getValues();
  const row = data.slice(1).find(r => r[0] === name);
  if (!row) return null;
  return { name: row[0], amount: row[2] };
}

請求書のレイアウトはテンプレート用のスプレッドシートを1枚用意し、それをコピーして値を差し込んでからPDF化しています。生成したPDFはGoogleドライブに保存しつつ、自分宛にメール添付で送るようにしています。

つまずいたポイント:ScriptApp.getOAuthToken()を使ってスプレッドシートのエクスポートURLを叩く部分で、初回実行時に権限不足のエラーが出ました。Driveへのアクセス権限が新たに必要になるため、実行時に表示される承認画面で権限を許可し直す必要がありました。

運用してみて

まだ数件しか試していませんが、案件名を指定して実行するだけで請求書が届くのは想像以上に快適でした。次は複数案件をまとめて処理できるようにするのが課題です。

よくある質問

PDFのデザインを変えられますか?

はい。テンプレート側のスプレッドシートのレイアウトを自由に編集すれば、そのまま生成されるPDFにも反映されます。

請求書番号は自動採番されますか?

今回はシンプルにするため未実装です。番号が必要な場合は、案件一覧の行番号などを組み合わせて採番する処理を追加で検討しています。

freeeやマネーフォワードのような会計ソフトと連携できますか?

今回はPDF出力とメール送信までをスコープにしており、会計ソフトへの直接連携は行っていません。

次回は、この8本を通して自動化してきた業務全体のビフォーアフターをまとめます。