🔰 講座03

GASでスプレッドシートを自動化してマスターデータから帳票を自動作成できるようになる

⏱ 想定学習時間 25時間(1日2時間 × 12〜13日)
📋 前提条件 講座02修了(Google AI Studio・Cursor が使える状態)
🛤 パス A・B共通
GAS帳票自動化フロー
📊 マスターシート
顧客・商品データ
⚡ GASスクリプト
自動転記処理
📄 帳票シート
見積書・請求書
📧 Gmail送信
PDF添付メール
1

Phase 1: GASの基礎とスプレッドシート操作

⏱ 約8時間
GAS(Google Apps Script)とは
Googleが提供する無料のプログラミング環境です。スプレッドシート・Gmail・Googleドライブと直接連携でき、サーバーなしでクラウド上で自動処理が実行されます。Googleアカウントがあれば今すぐ使えます。
⚠️ GASの1日あたりの主な実行制限(無料枠)
メール送信: 100通/日 / トリガー実行: 90分/日(累計) / URLフェッチ: 20,000回/日 / スクリプト1回の実行: 最大6分
1-1

STEP 1-1: はじめてのGASスクリプト作成

Googleスプレッドシートのメニューバー「拡張機能」→「Apps Script」をクリックすると新しいタブでGASエディタが開きます。以下のコードを貼り付けて実行してみましょう。

Apps Script — はじめてのGAS.gs
SCRIPTS
📄 コード.gs
📄 コード.gs
1// はじめてのGASスクリプト
2function helloGAS() {
3 const ss = SpreadsheetApp.getActiveSpreadsheet();
4 const sheet = ss.getActiveSheet();
5 sheet.getRange('A1').setValue('こんにちは、GAS!');
6 const now = new Date();
7 sheet.getRange('B1').setValue(now);
8 console.log('スクリプト実行完了');
9}

▲ Google Apps Script のエディタ画面

Google Apps Script コード.gs
// はじめてのGASスクリプト
function helloGAS() {
  // アクティブなスプレッドシートを取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getActiveSheet();

  // A1セルに文字を書き込む
  sheet.getRange('A1').setValue('こんにちは、GAS!');

  // B1セルに現在の日時を書き込む
  const now = new Date();
  sheet.getRange('B1').setValue(now);

  // ログに出力(デバッグ用)
  console.log('スクリプト実行完了');
  Logger.log('実行日時: ' + now);
}
実行手順
1. コードを貼り付けたら「💾 保存」ボタン(Ctrl+S)を押す
2. 「▶ 実行」ボタンを押す
3. 初回は「承認が必要です」ダイアログ → 「権限を確認」→ Googleアカウントを選択 → 「詳細」→「(安全でないページ)に移動」→「許可」
4. スプレッドシートのA1セルに「こんにちは、GAS!」と表示されれば成功

Google AI StudioでGASコードを生成するプロンプト例:

プロンプト例 Google AI Studio
あなたはGoogle Apps Script(GAS)の専門家です。
以下の要件を満たすGASコードを書いてください。

【要件】
- Googleスプレッドシートの「データ」シートのA列に入力された名前と
  B列に入力された数値を読み取り
- C列に「名前: ○○ / 数値: ○○」という形式でテキストを書き込む
- 空白行が出てきたら処理を止める

【出力形式】
- コードのみ(説明不要)
- コメントを日本語で記述すること
⚠️ エラーが出た場合

「このアプリはGoogleで確認されていません」と表示される
→ これは正常な動作です。「詳細」→「(安全でないページ)に移動」を選んでください。自分で作ったスクリプトなので安全です。

「TypeError: Cannot read properties of undefined」
→ シート名が合っていない可能性があります。ss.getActiveSheet() の代わりに ss.getSheetByName('シート名') で明示的に指定してください。

実行しても何も起きない
→ エディタ上部のドロップダウンで実行したい関数名(例: helloGAS)が選択されているか確認してください。

Google AI Studioで生成したコードをどこに貼ればよいか分からない
→ GASエディタの function myFunction() {} 部分を全選択して削除し、生成されたコードを貼り付けてください。

1-2

STEP 1-2: スプレッドシートのデータ操作

GASでスプレッドシートのデータを読み書きする基本的な方法を学びます。getRangegetValuessetValues を使いこなすことが自動化の核心です。

🔒 https://aistudio.google.com
モデル
🤖 Gemini 2.0 Flash ▼
System instruction
GASのエキスパートとして、スプレッドシート操作コードを日本語コメント付きで生成してください。
あなた
A1:C10の範囲を読み取って空白行をスキップしながらループするGASを書いて
AI
以下のコードを使用してください。getRange('A1:C10').getValues()で2次元配列として取得し、forEachでループします...
プロンプトを入力...

▲ Google AI Studio でGASのデータ操作コードを生成

Google Apps Script データ操作.gs
function readData() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('データ');

  // 単一セルの読み取り
  const singleValue = sheet.getRange('A1').getValue();
  console.log('A1の値: ' + singleValue);

  // 複数セルを2次元配列で取得(A1からC10まで)
  const rangeValues = sheet.getRange('A1:C10').getValues();

  // 配列をループして処理
  rangeValues.forEach(function(row, rowIndex) {
    if (row[0] !== '') {  // 空白行はスキップ
      console.log('行' + (rowIndex + 1) + ': ' + row[0] + ', ' + row[1]);
    }
  });

  // データが入っている最終行を取得
  const lastRow = sheet.getLastRow();
  console.log('最終行: ' + lastRow);
}

function writeData() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('データ');

  // まとめて書き込む(setValues)※2次元配列で渡す
  const writeValues = [
    ['田中 太郎', 10000],
    ['鈴木 花子', 25000],
    ['佐藤 次郎', 15000],
  ];
  sheet.getRange(2, 1, writeValues.length, 2).setValues(writeValues);
  console.log('書き込み完了');
}
⚠️ エラーが出た場合

「Exception: The number of rows in the range must be at least 1」
getLastRow() が1(ヘッダーのみ)の場合、データが0件です。if (lastRow < 2) return; で早期リターンしてください。

getValuesで取得した配列が思ったサイズと違う
getRange(startRow, startCol, numRows, numCols) の引数の順番を確認してください。行番号→列番号→行数→列数の順です。

setValuesで「The number of columns in the data does not match」エラー
setValues に渡す2次元配列の列数と getRange で指定した列数が一致していないことが原因です。

1-3

STEP 1-3: カスタムメニューの作成

onOpen 関数を使うと、スプレッドシートを開いたときに自動でカスタムメニューが追加されます。これにより、GASの機能をボタン感覚で呼び出せるようになります。

Apps Script — 帳票自動化.gs
SCRIPTS
📄 コード.gs
📄 ユーティリティ.gs
📄 コード.gs
1function onOpen() {
2 const ui = SpreadsheetApp.getUi();
3 ui.createMenu('帳票自動化')
4 .addItem('見積書を作成', 'createEstimate')
5 .addItem('請求書を作成', 'createInvoice')
6 .addSeparator()
7 .addItem('月次レポート送信', 'sendMonthlyReport')
8 .addToUi();
9}

▲ onOpen関数でカスタムメニューを追加するコード

Google Apps Script カスタムメニュー.gs
// スプレッドシートを開いたときに自動実行される特別な関数
function onOpen() {
  const ui = SpreadsheetApp.getUi();

  // 「帳票自動化」というメニューを追加
  ui.createMenu('帳票自動化')
    .addItem('見積書を作成', 'createEstimate')
    .addItem('請求書を作成', 'createInvoice')
    .addSeparator()
    .addItem('月次レポート送信', 'sendMonthlyReport')
    .addToUi();
}

// 入力ダイアログ(テキスト入力)
function promptExample() {
  const ui = SpreadsheetApp.getUi();
  const result = ui.prompt('顧客IDを入力してください');

  if (result.getSelectedButton() === ui.Button.OK) {
    const inputText = result.getResponseText();
    console.log('入力値: ' + inputText);
  }
}
ポイント: onOpen は保存後、スプレッドシートを一度閉じて再度開くとメニューが表示されます。GASエディタから手動で onOpen を実行してもメニューは追加されません。
⚠️ エラーが出た場合

メニューが表示されない
onOpen 関数を保存後、スプレッドシートを一度閉じて再度開いてください。

「TypeError: ui.createMenu is not a function」
SpreadsheetApp.getUi() が正しい書き方です。ss.getUi() ではありません。

スクリプトを保存したのにメニューに反映されない
→ ブラウザのキャッシュが残っている場合があります。Ctrl+Shift+R(Mac: Cmd+Shift+R)で強制リロードしてみてください。

✅ Phase 1 修了条件チェックリスト
☐ GASエディタを開いてHello Worldを実行できた
☐ スプレッドシートのA1セルに文字を書き込めた
☐ getValuesで複数セルのデータを配列で取得できた
☐ onOpen関数でカスタムメニューが表示された
☐ Browser.msgBoxで確認ダイアログを表示できた
☐ console.log / Logger.logでデバッグログを確認できた
2

Phase 2: マスターデータから帳票を自動生成

⏱ 約9時間
マスターデータシートの設計
スプレッドシートに以下の4つのシートを作成してください:
顧客マスター: 顧客ID / 顧客名 / 会社名 / 郵便番号 / 住所 / メールアドレス
商品マスター: 商品ID / 商品名 / 単価 / 単位 / 税率
番号管理: 種別(見積書/請求書) / 最終番号
見積書テンプレート: 帳票フォーマットのベース
2-1

STEP 2-1: 見積書の自動作成

顧客マスターから顧客情報を検索し、見積書テンプレートをコピーして新しいシートを作成します。見積番号は「EST-0001」形式で自動採番されます。

Apps Script — 見積書自動作成.gs
SCRIPTS
📄 コード.gs
📄 ユーティリティ.gs
📄 コード.gs
1function createEstimate() {
2 const ss = SpreadsheetApp.getActiveSpreadsheet();
3 const customer = customerSheet.getDataRange().getValues();
4 const estimateNo = getNextNumber('見積書');
5 const newSheet = templateSheet.copyTo(ss);
6 newSheet.setName('見積書_' + estimateNo);
7 newSheet.getRange('B2').setValue(estimateNo);
8 // 顧客情報・日付を書き込む...
9}

▲ 見積書自動作成スクリプトのコード例

💡
上図はイメージ図です(クリック・操作不可)
Apps Scriptエディタの画面イメージです。実際にコードを動かすには、下のコードブロックをコピーしてGASエディタに貼り付けてから実行してください。
初心者向け手順: Google スプレッドシートを開く → 上メニューの「拡張機能」→「Apps Script」→ エディタが開いたらコードを貼り付けて「保存」→「実行」
Google Apps Script 見積書自動作成.gs
/**
 * 見積番号を採番して返す
 */
function getNextNumber(type) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const mgmtSheet = ss.getSheetByName('番号管理');
  const data = mgmtSheet.getDataRange().getValues();

  for (let i = 1; i < data.length; i++) {
    if (data[i][0] === type) {
      const nextNum = data[i][1] + 1;
      mgmtSheet.getRange(i + 1, 2).setValue(nextNum);
      const prefix = type === '見積書' ? 'EST' : 'INV';
      return prefix + '-' + String(nextNum).padStart(4, '0');
    }
  }
  return null;
}

/**
 * 見積書を自動作成する
 */
function createEstimate() {
  const ui = SpreadsheetApp.getUi();
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  // 顧客IDを入力させる
  const promptResult = ui.prompt('顧客IDを入力してください(例: C001)');
  if (promptResult.getSelectedButton() !== ui.Button.OK) return;
  const customerId = promptResult.getResponseText().trim();

  // 顧客マスターから顧客情報を検索
  const customerSheet = ss.getSheetByName('顧客マスター');
  const customerData = customerSheet.getDataRange().getValues();
  const customer = customerData.find(row => row[0] === customerId);

  if (!customer) {
    ui.alert('エラー', '顧客ID「' + customerId + '」は見つかりませんでした。', ui.ButtonSet.OK);
    return;
  }

  // テンプレートをコピーして新しいシートを作成
  const templateSheet = ss.getSheetByName('見積書テンプレート');
  const estimateNo = getNextNumber('見積書');
  const newSheet = templateSheet.copyTo(ss);
  newSheet.setName('見積書_' + estimateNo);

  // 発行日・見積番号・顧客情報を書き込む
  const today = new Date();
  const formattedDate = Utilities.formatDate(today, 'Asia/Tokyo', 'yyyy年MM月dd日');
  newSheet.getRange('B2').setValue(estimateNo);
  newSheet.getRange('B3').setValue(formattedDate);
  newSheet.getRange('B5').setValue(customer[2]);       // 会社名
  newSheet.getRange('B6').setValue(customer[1] + ' 様'); // 顧客名

  ui.alert('完了', '見積書「' + estimateNo + '」を作成しました。', ui.ButtonSet.OK);
  ss.setActiveSheet(newSheet);
}

/**
 * アクティブシートをPDFとして保存する
 */
function exportToPDF() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getActiveSheet();
  SpreadsheetApp.flush(); // 計算を確定させる
  const pdfBlob = sheet.getAs(MimeType.PDF);
  pdfBlob.setName(sheet.getName() + '.pdf');
  const file = DriveApp.createFile(pdfBlob);
  SpreadsheetApp.getUi().alert('PDF保存完了', 'URL:\n' + file.getUrl(), SpreadsheetApp.getUi().ButtonSet.OK);
}
⚠️ エラーが出た場合

「Exception: You do not have permission to call DriveApp.createFile」
→ GASエディタの「プロジェクトの設定」→「スコープ」で https://www.googleapis.com/auth/drive が含まれているか確認し、再度承認してください。

テンプレートシートが見つからない(null)
getSheetByName('見積書テンプレート') のシート名がスプレッドシート上のタブ名と完全一致しているか確認してください。全角スペース・半角スペースの違いに注意。

getAs(MimeType.PDF) でエラーになる
'application/pdf' と文字列で書くことでも代用できます。

PDFが空白で出力される
SpreadsheetApp.flush() をPDF出力前に呼び出してください。

2-2

STEP 2-2: 請求書・納品書の自動作成(一括生成)

Utilities.formatDate で日付を整形し、全顧客に対して請求書を一括生成する関数を実装します。支払期限は翌月末を自動計算します。

Google Apps Script 請求書一括生成.gs
/**
 * 日付フォーマットのサンプル集
 */
function dateFormatExamples() {
  const now = new Date();
  const tz = 'Asia/Tokyo';
  console.log(Utilities.formatDate(now, tz, 'yyyy年MM月dd日'));        // 2026年03月19日
  console.log(Utilities.formatDate(now, tz, 'yyyy/MM/dd'));            // 2026/03/19
  // 翌月末(支払期限に使用)
  const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 2, 0);
  console.log(Utilities.formatDate(lastDayOfMonth, tz, 'yyyy年MM月dd日'));
}

/**
 * 全顧客に対して請求書を一括生成する
 */
function createAllInvoices() {
  const ui = SpreadsheetApp.getUi();
  const response = ui.alert('一括生成確認', '全顧客分の請求書を一括生成します。よろしいですか?', ui.ButtonSet.YES_NO);
  if (response !== ui.Button.YES) return;

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const customerSheet = ss.getSheetByName('顧客マスター');
  const lastRow = customerSheet.getLastRow();
  if (lastRow < 2) { ui.alert('顧客マスターにデータがありません。'); return; }

  const customers = customerSheet.getRange(2, 1, lastRow - 1, 6).getValues();
  let createdCount = 0;

  try {
    customers.forEach(function(customer) {
      if (customer[0] === '') return;
      const invoiceNo = getNextNumber('請求書');
      const templateSheet = ss.getSheetByName('請求書テンプレート');
      const newSheet = templateSheet.copyTo(ss);
      newSheet.setName('請求書_' + invoiceNo);
      const today = new Date();
      const dueDate = new Date(today.getFullYear(), today.getMonth() + 2, 0);
      const tz = 'Asia/Tokyo';
      newSheet.getRange('B2').setValue(invoiceNo);
      newSheet.getRange('B3').setValue(Utilities.formatDate(today, tz, 'yyyy年MM月dd日'));
      newSheet.getRange('B4').setValue(Utilities.formatDate(dueDate, tz, 'yyyy年MM月dd日'));
      newSheet.getRange('B5').setValue(customer[2]);
      newSheet.getRange('B6').setValue(customer[1] + ' 様');
      SpreadsheetApp.flush();
      exportToPDFInFolder(newSheet, '請求書_' + Utilities.formatDate(today, tz, 'yyyyMM'));
      createdCount++;
    });
    ui.alert('完了', createdCount + '件の請求書を作成しました。', ui.ButtonSet.OK);
  } catch (e) {
    ui.alert('エラーが発生しました: ' + e.message);
  }
}
⚠️ エラーが出た場合

一括生成が途中で止まる
→ GASの1回の実行時間制限は6分です。顧客数が多い場合は処理が中断されます。処理の進捗を「番号管理」シートに記録して中断した番号から再開できるよう設計してください。

同じシート名が既に存在してエラーになる
setName 前に同名シートがないか確認してください。

PDFのレイアウトが崩れる
→ テンプレートシートで「ファイル」→「印刷」→用紙サイズ・余白を設定してから再試行してください。

2-3

STEP 2-3: 進捗表の自動更新

onEdit トリガーを使って、「進捗管理」シートのステータス列が変更されたら自動で日時を記録し、行の背景色を変更します。

Google Apps Script 進捗管理.gs
/**
 * onEdit トリガー: ステータス変更を検知して日付を自動記録
 */
function onEdit(e) {
  const sheet = e.source.getActiveSheet();
  if (sheet.getName() !== '進捗管理') return;

  const editedCol = e.range.getColumn();
  const editedRow = e.range.getRow();

  // C列(ステータス列)が編集された場合
  if (editedCol === 3 && editedRow >= 2) {
    const status = e.range.getValue();
    const dateStr = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm');
    sheet.getRange(editedRow, 4).setValue(dateStr);
    if (status === '完了') { sheet.getRange(editedRow, 5).setValue(dateStr); }
    setRowColor(sheet, editedRow, status);
  }
}

/**
 * ステータスに応じて行の背景色を変更する
 */
function setRowColor(sheet, row, status) {
  const colorMap = {
    '未着手': '#ffffff',
    '進行中': '#fff3cd',
    '確認中': '#cce5ff',
    '完了':   '#d4edda',
    '中断':   '#f8d7da',
  };
  const color = colorMap[status] || '#ffffff';
  sheet.getRange(row, 1, 1, sheet.getLastColumn()).setBackground(color);
}
⚠️ エラーが出た場合

onEdit が動かない
→ GASエディタから手動実行するとエラーになります(e オブジェクトが存在しないため)。スプレッドシートのセルを実際に編集して動作を確認してください。

onEdit でGmailやドライブの操作ができない
→ シンプルトリガーは権限が制限されています。GmailやDriveを使う場合はインストール可能トリガー(STEP 3-1参照)を使ってください。

setBackground が反映されない
setBackgroundColor というメソッドは存在しません。setBackground(引数は16進カラーコード)が正しいメソッド名です。

✅ Phase 2 修了条件チェックリスト
☐ 顧客マスター・商品マスターシートを設計して作成できた
☐ 見積書テンプレートシートからコピーして帳票を自動生成できた
☐ 見積番号を自動採番する仕組みを実装できた
☐ 生成した帳票をGoogleドライブにPDFとして保存できた
☐ 請求書の日付・番号を自動設定できた
☐ 進捗管理シートのステータス変更で自動日付記録ができた
☐ setBackgroundColorで条件付き書式を自動設定できた
3

Phase 3: トリガーとGmail連携

⏱ 約8時間
3-1

STEP 3-1: トリガーの設定(自動実行)

時間ベーストリガーを設定することで、毎日・毎月など指定した時間に関数を自動実行できます。GASエディタの左メニュー「⏰ トリガー」から設定します。

Apps Script — トリガー設定.gs
SCRIPTS
📄 トリガー.gs
📄 トリガー.gs
1function createTriggers() {
2 deleteTriggers('dailyCheck');
3 ScriptApp.newTrigger('dailyCheck')
4 .timeBased().everyDays(1).atHour(8).create();
5 console.log('トリガーを登録しました');
6}

▲ トリガー設定スクリプト

Google Apps Script トリガー設定.gs
/**
 * トリガーをコードから登録する(1回だけ実行する)
 */
function createTriggers() {
  deleteTriggers('dailyCheck');

  ScriptApp.newTrigger('dailyCheck')
    .timeBased()
    .everyDays(1)
    .atHour(8)
    .create();

  console.log('トリガーを登録しました');
}

/**
 * 指定した関数名のトリガーを全削除
 */
function deleteTriggers(funcName) {
  ScriptApp.getProjectTriggers().forEach(trigger => {
    if (trigger.getHandlerFunction() === funcName) {
      ScriptApp.deleteTrigger(trigger);
    }
  });
}
トリガー種別一覧
onOpen: スプレッドシートを開いたとき(カスタムメニューの追加)
onEdit: セルを編集したとき(ステータス変更の検知)
onChange: シート構造が変わったとき
時間ベース: 設定した時間に(定期実行、月次レポート)
⚠️ エラーが出た場合

トリガーが実行されない
→ GASエディタの「実行」→「実行履歴」でトリガーの実行状況を確認できます。

「承認が必要です」メールが届く
→ GASエディタを開いて手動で一度関数を実行し、承認ダイアログを完了させてください。

トリガーが二重に登録されてしまう
deleteTriggers で一度削除してから登録し直してください。

3-2

STEP 3-2: GmailからのメールをGASで自動処理

GmailApp.search() で条件に合うメールを取得し、スプレッドシートに記録します。「発注」が件名に含まれる未読メールを自動で取り込む例です。

Google Apps Script メール取込.gs
/**
 * 受信メールを読み取ってスプレッドシートに記録する
 */
function importEmailsToSheet() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  let sheet = ss.getSheetByName('メール受信ログ');
  if (!sheet) {
    sheet = ss.insertSheet('メール受信ログ');
    sheet.appendRow(['受信日時', '送信者', '件名', '本文(先頭200文字)', '処理済み']);
  }

  try {
    // 件名に「発注」が含まれる未読メールを最大10件取得
    const threads = GmailApp.search('subject:発注 is:unread', 0, 10);

    threads.forEach(thread => {
      thread.getMessages().forEach(message => {
        const date = message.getDate();
        const from = message.getFrom();
        const subject = message.getSubject();
        const body = message.getPlainBody().substring(0, 200);
        const dateStr = Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd HH:mm');
        sheet.appendRow([dateStr, from, subject, body, '未処理']);
        message.markRead();
      });
    });

    console.log('メール取り込み完了: ' + threads.length + '件');
  } catch (e) {
    console.error('メール取り込みエラー: ' + e.message);
  }
}
⚠️ エラーが出た場合

「Exception: You do not have permission to call GmailApp」
→ エディタから手動で一度実行して承認フローを完了させてください。

threads.length が常に0になる
→ まず GmailApp.getInboxThreads(0, 5) で受信トレイの先頭5件を取得して動作確認してください。

3-3

STEP 3-3: GmailへのメールをGASで自動送信

GmailApp.sendEmail() で請求書PDFを添付してメールを送信します。送信履歴をシートに自動記録する仕組みも実装します。

🔒 https://aistudio.google.com
モデル
🤖 Gemini 2.0 Flash ▼
System instruction
GASのエキスパートとして、try-catch・JSDocコメント・エラー通知メールを含む完全なコードを生成してください。
あなた
請求書シートをPDF化して顧客のメールアドレスに添付送信するGASを書いて。送信履歴もシートに記録して。
AI
以下のGASコードを作成しました。sendInvoiceEmail関数でPDF添付メールを送信し、logEmailSent関数で送信履歴を記録します...
プロンプトを入力...

▲ Google AI Studio でメール送信GASを生成

Google Apps Script メール送信.gs
/**
 * 請求書PDFをGmailに添付して顧客に送信する
 */
function sendInvoiceEmail(invoiceSheetName, toEmail, customerName) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const invoiceSheet = ss.getSheetByName(invoiceSheetName);
  if (!invoiceSheet) { console.error('シートが見つかりません: ' + invoiceSheetName); return false; }

  try {
    SpreadsheetApp.flush();
    const pdfBlob = invoiceSheet.getAs(MimeType.PDF);
    pdfBlob.setName(invoiceSheetName + '.pdf');

    const body = customerName + ' 様\n\nお世話になっております。\n下記の通り、請求書をお送りいたします。\n\n添付のPDFをご確認ください。\n\nどうぞよろしくお願いいたします。';

    GmailApp.sendEmail(toEmail, '【請求書送付】' + invoiceSheetName, body, {
      attachments: [pdfBlob],
      name: '自動送信システム'
    });

    logEmailSent(toEmail, customerName, invoiceSheetName);
    console.log('請求書メール送信完了: ' + toEmail);
    return true;
  } catch (e) {
    console.error('メール送信エラー: ' + e.message);
    return false;
  }
}

/**
 * エラー発生時に通知メールを送る
 */
function sendErrorNotification(functionName, error) {
  const adminEmail = getAdminEmail();
  const now = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss');
  GmailApp.sendEmail(adminEmail,
    '【GAS エラー通知】' + functionName,
    '発生日時: ' + now + '\n関数名: ' + functionName + '\nエラー内容: ' + error.message
  );
}
⚠️ エラーが出た場合

「Service invoked too many times in one day: email」
→ GmailAppのメール送信は1日100通の制限があります。複数日に分けて実行する仕組みを追加してください。

添付ファイルサイズエラー
→ Gmailの添付ファイル上限は25MBです。不要な画像を削除するか印刷範囲を絞り込んでください。

メールが送信されるが添付ファイルが壊れている
SpreadsheetApp.flush()getAs() の直前に必ず呼び出してください。

3-4

STEP 3-4: 実践 - 月次レポートの完全自動化

月末に自動集計→PDF作成→メール送信を一連のフローで実行します。トリガーは毎月28日UTC13時(日本時間22時)に設定します。

月次レポート自動化フロー
📅 月末トリガー
自動発火
📊 月次集計
売上データ集計
📄 PDF作成
レポートシート
📧 メール送信
管理者へ通知
Google Apps Script 月次レポート.gs
/**
 * 月次レポートの完全自動化メイン関数
 * トリガー:毎月末日 22:00 に実行
 */
function sendMonthlyReport() {
  const tz = 'Asia/Tokyo';
  const now = new Date();
  const yearMonth = Utilities.formatDate(now, tz, 'yyyy年MM月');

  try {
    // Step 1: 月次データを集計する
    const reportData = aggregateMonthlyData(now);
    // Step 2: レポートシートを作成・更新する
    const reportSheet = createMonthlyReportSheet(yearMonth, reportData);
    // Step 3: PDFとして保存する
    SpreadsheetApp.flush();
    const pdfFile = exportToPDFInFolder(reportSheet, '月次レポート');
    // Step 4: 管理者にメール送信する
    const adminEmail = getAdminEmail();
    sendReportEmail(adminEmail, yearMonth, pdfFile);
    // Step 5: 送信完了をシートに記録する
    logEmailSent(adminEmail, '管理者', yearMonth + 'レポート');
    console.log(yearMonth + ' 月次レポート送信完了');
  } catch (e) {
    sendErrorNotification('月次レポート送信', e);
  }
}
⚠️ エラーが出た場合

月次レポートが実行されない(トリガーが発火しない)
→ トリガーの「時刻」はGMT(UTC)で設定されます。日本時間22時に実行したい場合はUTC 13時(22 - 9 = 13)を設定してください。

「Exception: Service Spreadsheets failed while accessing document」
→ 他のユーザーが編集中の場合に発生します。LockService を使って排他制御を検討してください。

毎月の集計データが前月分と混在してしまう
→ 終了日に 23:59:59 を加えるか < で翌月1日と比較してください。

✅ Phase 3 修了条件チェックリスト
☐ 時間ベーストリガーを設定して毎日自動実行できた
☐ onEditトリガーでシート変更を検知できた
☐ GmailApp.getInboxThreads()でメールを取得してシートに記録できた
☐ GmailApp.sendEmail()でメールを送信できた
☐ 請求書PDFをGmailに添付して送信できた
☐ try-catchでエラー通知メールを実装できた
☐ 月次レポートの完全自動化フローを実装できた

🎓 講座03 全体修了条件チェックリスト

← 前の講座:02 AIツールセットアップ 次の講座:04 Google AI Studio アプリ開発 →