【コピペでOK!】スプレッドシートの小口現金管理を完全自動化!差異があればGASがメールで自動通知

GoogleAppsScript

前月の締め作業のために開いた小口現金管理のファイル・・・。

「あれ、数百円の差異がある…」

単純な入力ミスの場合もあれば、担当者に原因を聞いてみても、「前のことだから覚えていない…」ということも・・・。

何とも言えない気持ちを抱えたまま、結局は原因不明の雑損失で処理するしかない…。

そんな経験はありませんか?

もし、差異が発生したその日のうちにあるいは次の日に、あなたの元へ自動で通知が届いていたら・・・

記憶が新しいうちに原因をすぐに確認でき、面倒な調査や「原因不明金」とはおさらばできるのではないでしょうか?

毎日のお仕事、お疲れ様です!
クロウルを運営している藤井貴司です。

この記事では、前回の記事【GAS不要/ステップ形式で解説】計算ミスと集計作業をなくすGoogleスプレッドシート小口現金管理システムの作り方で作った小口現金管理シートを使います。

前回の記事って?という方はこちらからどうぞ!

この小口現金管理シートを使い、Google Apps Script(GAS)で現金有高と帳簿残高の差異を自動メールで通知するシステムをの作り方を、誰でもできるようにイチから丁寧に解説します!

今回の記事を読む上で、プログラミングの知識は一切不要です。

この記事のコードをコピー&ペーストするだけで、あなたの小口現金管理をより正確で、スピーディーなものに変えることができます。

もし、違う用途で使いたいと言った場合でも、AI(GeminiやChatGPTなど)にコードと変更したいことを伝えれば、あなたに合ったコードを作ってくれますよ。

完成イメージ:こんな感じです

スプレッドシートに専用のメニューが追加され、そこから通知のタイミングやメールの文面を設定できる、非常に使いやすいツールが完成します。

メールの宛先や件名、本文も、以下のような専用のフォームから簡単に設定できるようになります。

そして、差異があった日には、設定した時刻に以下のような通知メールが自動で届きます。

それでは一緒に作っていきましょう!!

STEP1:スクリプトエディタを開く

まずは、GASのコードを書き込むための場所を開きます。

1.小口現金管理シートを開きます。

2.上部メニューの「拡張機能」から「Apps Script」を選択します。

3.新しいタブで、スクリプトエディタという画面が開けば準備完了です。
 ちなみに最初は開くまでに時間がかかることがるので、開くまで待っていてください。

4.スクリプトエディタが開いたら、「無題のプロジェクト」を「小口現金管理シート」に変更してください。

STEP2:設定画面の「見た目」を作る (HTMLファイル2つ)

次に、ユーザーが設定を行うためのダイアログ画面の設計図となるHTMLファイルを2つ作成します。

① 通知時刻設定ダイアログの作成

1.STEP1で開いたエディタ左側の「ファイル」の横にある「+」アイコンをクリックし、「HTML」を選択します。

2.ファイル名に「SettingDialog」と入力してEnterキーを押します。

3.新しく作成されたSettingDialog.htmlの中身をすべて削除し、以下のコードをそのまま貼り付けて、保存してください。
ファイル」の横の方にある「フロッピーディスクのアイコン」か「ctrl+S」で出来ます。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
      body { font-family: sans-serif; padding: 20px; }
      select, button { width: 100%; padding: 8px; margin-top: 10px; box-sizing: border-box; }
      button.action { background-color: #4285f4; color: white; border: none; cursor: pointer; }
      button.cancel { background-color: #f1f1f1; color: black; border: 1px solid #ccc; cursor: pointer; }
      #status { margin-top: 15px; font-size: 0.9em; text-align: center; }
    </style>
  </head>
  <body>
    <p>通知を受け取りたい時間帯を選択してください。</p>
    <select id="hour-select"></select>
    <button id="save-btn" class="action" onclick="saveSettings()">設定を保存</button>
    <button class="cancel" onclick="google.script.host.close()">キャンセル</button>
    <div id="status"></div>
    <script>
      document.addEventListener("DOMContentLoaded", function() {
        google.script.run.withSuccessHandler(function(currentHour) {
          const select = document.getElementById("hour-select");
          for (let i = 0; i <= 23; i++) {
            const option = document.createElement("option");
            option.value = i; option.text = `毎日 ${i}時台`;
            if (i === currentHour) option.selected = true;
            select.appendChild(option);
          }
        }).getCurrentTriggerHour();
      });
      function saveSettings() {
        const saveBtn = document.getElementById("save-btn");
        const statusDiv = document.getElementById("status");
        saveBtn.disabled = true;
        statusDiv.textContent = "設定を更新中...";
        const selectedHour = document.getElementById("hour-select").value;
        google.script.run
          .withSuccessHandler(function(response) {
            statusDiv.textContent = response;
            setTimeout(function() { google.script.host.close(); }, 1500);
          })
          .withFailureHandler(function(error) {
            statusDiv.textContent = 'エラー: ' + error.message;
            saveBtn.disabled = false;
          })
          .updateTrigger(selectedHour);
      }
    </script>
  </body>
</html>

② メール内容設定ダイアログの作成

1.上と同じように、「+」から「HTML」を選択し、今度は「MailTemplateDialog」という名前で新しいファイルを作成します。

2.作成されたファイルの中身をすべて削除し、以下のコードを貼り付けて、保存してください。

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
      body { font-family: sans-serif; padding: 10px 20px; }
      .container { display: flex; flex-direction: column; height: 95vh; }
      .content { flex-grow: 1; overflow-y: auto; }
      .footer { flex-shrink: 0; padding-top: 10px; }
      label { font-weight: bold; margin-top: 15px; display: block; }
      input, textarea { width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; }
      textarea { height: 100px; resize: vertical; }
      button { padding: 8px 12px; margin-top: 10px; cursor: pointer; border-radius: 4px; border: none; }
      .action { background-color: #4285f4; color: white; }
      .cancel { background-color: #f1f1f1; }
      .keywords { background-color: #f9f9f9; border: 1px solid #ddd; padding: 10px; margin-top: 10px; font-size: 0.9em; border-radius: 4px; }
      #status { margin-top: 10px; text-align: center; }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="content">
        <label for="to">TO:</label>
        <textarea id="to" placeholder="例: tanaka@example.com, suzuki@example.com"></textarea>

        <label for="cc">CC:</label>
        <textarea id="cc" placeholder="(任意)カンマ区切りで複数指定可"></textarea>

        <label for="subject">件名:</label>
        <input type="text" id="subject">

        <label for="body">本文:</label>
        <textarea id="body"></textarea>

        <div class="keywords">
          <b>使えるキーワード:</b><br>
          <code>{{シート名}}</code> <code>{{日付}}</code> <code>{{過不足額}}</code> <code>{{シートURL}}</code>
        </div>
      </div>
      <div class="footer">
        <button id="save-btn" class="action" onclick="saveSettings()">設定を保存</button>
        <button class="cancel" onclick="google.script.host.close()">キャンセル</button>
        <div id="status"></div>
      </div>
    </div>
    <script>
      // ダイアログが開かれたときに実行
      document.addEventListener("DOMContentLoaded", function() {
        google.script.run.withSuccessHandler(function(settings) {
          document.getElementById("to").value = settings.to || '';
          document.getElementById("cc").value = settings.cc || '';
          document.getElementById("subject").value = settings.subject || '';
          document.getElementById("body").value = settings.body || '';
        }).getAllSettings();
      });

      // 「保存」ボタンが押されたときの処理
      function saveSettings() {
        const saveBtn = document.getElementById("save-btn");
        const statusDiv = document.getElementById("status");
        saveBtn.disabled = true;
        statusDiv.textContent = "設定を保存中...";

        const settings = {
          to: document.getElementById("to").value,
          cc: document.getElementById("cc").value,
          subject: document.getElementById("subject").value,
          body: document.getElementById("body").value
        };

        google.script.run
          .withSuccessHandler(function(response) {
            statusDiv.textContent = response;
            setTimeout(function() { google.script.host.close(); }, 1500);
          })
          .withFailureHandler(function(error) {
            statusDiv.textContent = 'エラー: ' + error.message;
            saveBtn.disabled = false;
          })
          .saveAllSettings(settings);
      }
    </script>
  </body>
</html>

STEP3:GASで自動メール送信の仕組みを作る

いよいよ、GASスクリプト本体を記述します。

1.スクリプトエディタで コード.gs ファイルを開きます。

2.中に書かれているコードをすべて消し、以下のコードはコピペしてください。

    //==============【設定項目】ここから==============
    const SHEET_NAME = '金種表';
    const DATE_COLUMN = 1;
    const DISCREPANCY_COLUMN = 13;
    //==============【設定項目】ここまで==============
    
    const scriptProperties = PropertiesService.getScriptProperties();
    
    // メニュー作成
    function onOpen() {
      SpreadsheetApp.getUi()
        .createMenu('⚙️ 自動通知設定')
        .addItem('メール内容の設定...', 'showMailTemplateDialog')
        .addItem('通知時刻の設定...', 'showSettingDialog')
        .addSeparator()
        .addItem('今すぐ差異をチェック', 'manualCheck')
        .addToUi();
    }
    function showMailTemplateDialog() {
      const html = HtmlService.createHtmlOutputFromFile('MailTemplateDialog').setWidth(500).setHeight(550);
      SpreadsheetApp.getUi().showModalDialog(html, 'メール内容の設定');
    }
    function showSettingDialog() {
      const html = HtmlService.createHtmlOutputFromFile('SettingDialog').setWidth(350).setHeight(230);
      SpreadsheetApp.getUi().showModalDialog(html, '通知時刻の設定');
    }
    function saveAllSettings(settings) {
      scriptProperties.setProperties(settings, true);
      return '設定を保存しました。';
    }
    function getAllSettings() {
      const defaultSettings = { to: '', cc: '', subject: '【要確認】小口現金に差異が発生しました', body: '「{{シート名}}」の金種表で現金過不足が検出されました。\n\n■ 日付: {{日付}}\n■ 過不足額: {{過不足額}} 円\n\n内容を確認してください。\n\n▼スプレッドシートを開く\n{{シートURL}}'};
      const currentSettings = scriptProperties.getProperties();
      return Object.keys(currentSettings).length > 0 ? currentSettings : defaultSettings;
    }
    function updateTrigger(hour) {
      const triggerHour = parseInt(hour, 10);
      const allTriggers = ScriptApp.getProjectTriggers();
      for (const trigger of allTriggers) { if (trigger.getHandlerFunction() === 'dailyCheckAndNotify') ScriptApp.deleteTrigger(trigger); }
      if (triggerHour >= 0 && triggerHour <= 23) {
        ScriptApp.newTrigger('dailyCheckAndNotify').timeBased().everyDays(1).atHour(triggerHour).create();
        return `通知時刻を毎日 ${triggerHour}時台に設定しました。`;
      } else { throw new Error('無効な時刻が選択されました。'); }
    }
    function getCurrentTriggerHour() {
      const triggers = ScriptApp.getProjectTriggers();
      for (const trigger of triggers) { if (trigger.getHandlerFunction() === 'dailyCheckAndNotify') return 16; }
      return 16;
    }
    function manualCheck() {
      dailyCheckAndNotify();
      SpreadsheetApp.getUi().alert('チェックが完了しました。');
    }
    
    
    /**
     * ★★★ 毎日定時に実行されるメイン処理 ★★★
     */
    function dailyCheckAndNotify() {
      const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
      if (!sheet) { return; }
      
      // ★★★ A列の値を基準に、本当にデータがある最終行を見つける ★★★
      const lastRow = getLastDataRow(sheet);
      
      if (lastRow <= 1) { return; }
    
      const discrepancyValue = sheet.getRange(lastRow, DISCREPANCY_COLUMN).getValue();
    
      if (discrepancyValue !== 0 && discrepancyValue !== '') {
        const date = new Date(sheet.getRange(lastRow, DATE_COLUMN).getValue());
        const discrepancyInfo = {
          date: Utilities.formatDate(date, 'JST', 'yyyy/MM/dd'),
          amount: discrepancyValue
        };
        sendDiscrepancyMail(discrepancyInfo);
      }
    }
    
    /**
     * ★★★ 書式設定に惑わされず、データのある最終行を取得する関数 ★★★
     */
    function getLastDataRow(sheet) {
      const columnValues = sheet.getRange("A:A").getValues(); // A列のすべての値を取得
      for (let i = columnValues.length - 1; i >= 0; i--) {
        if (columnValues[i][0] !== "") { // A列が空でない最後の行を探す
          return i + 1; // 配列のインデックスは0から始まるので+1する
        }
      }
      return 0; // データが全くない場合
    }
    
    /**
     * ★★★ メールを送信する関数 ★★★
     */
    function sendDiscrepancyMail(discrepancyInfo) {
      const settings = scriptProperties.getProperties();
      if (!settings.to) {
        console.error('送信エラー: TOの宛先が設定されていません。');
        return;
      }
      
      const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
      const spreadsheetName = spreadsheet.getName();
      const spreadsheetUrl = spreadsheet.getUrl();
    
      // テンプレートのキーワードを実際の情報に置き換える
      const subject = (settings.subject || '')
        .replace(/{{シート名}}/g, spreadsheetName)
        .replace(/{{日付}}/g, discrepancyInfo.date)
        .replace(/{{過不足額}}/g, discrepancyInfo.amount.toLocaleString())
        .replace(/{{シートURL}}/g, spreadsheetUrl);
      
      const body = (settings.body || '')
        .replace(/{{シート名}}/g, spreadsheetName)
        .replace(/{{日付}}/g, discrepancyInfo.date)
        .replace(/{{過不足額}}/g, discrepancyInfo.amount.toLocaleString())
        .replace(/{{シートURL}}/g, spreadsheetUrl);
      
      const mailOptions = { cc: settings.cc || '' };
      MailApp.sendEmail(settings.to, subject, body, mailOptions);
    }

    この画像の状態だと保存出来ていません。

    STEP4:使い方と初回設定

    これで準備は完了です。

    それでは、初回設定と使い方を説明します。

    1.スプレッドシートのブラウザタブを再読み込み(リロード)してください。

    2.上部メニューに「⚙️ 自動通知設定」という新しい項目が表示されます。

    表示されるまでに少し間があるので、待っていてください。

    3.まずは「⚙️ 自動通知設定」→「メール内容の設定…」を選びます。

    4.(初回のみ)Googleによる承認が求められます。

    5.(初回のみ)画面の指示に従い、ご自身のアカウントを選択して「許可」をクリックしてください。

    6.(初回のみ)「このアプリはGoogleで確認されていません」と表示されたら、「詳細」→「(プロジェクト名)に移動」をクリックして進めます

    7.「メール内容の設定」ダイアログに宛先や件名、本文を自由に入力して保存します。

    8.次に「通知時刻の設定…」を選び、通知してほしい時間帯を選んで保存します。

    これで、毎日あなたが設定した時間に、スクリプトが自動で残高をチェックし、差異があればメールでお知らせしてくれます。

    すぐに動作を試したい場合は、「⚙️ 自動通知設定」>「今すぐ差異をチェック」をクリックすれば、時間に関係なく手動でチェックを実行できます。

    おわりに

    お疲れ様でした!

    これで、日々の面倒な残高チェックから解放され、万が一の差異にもすぐに気づける、あなただけの強力なアシスタントが完成しました。

    Google Apps Scriptは、このように日々の業務の「ちょっと面倒」を解決する大きな可能性を秘めています。

    ぜひ今回の仕組みをきっかけに、色々な自動化にチャレンジしてみてください!