請求予定日の自動メール通知 GASコード
今回は、GoogleスプレッドシートとGoogle Apps Script(GAS)を連携させて「取引先への請求予定日が近づくと、自動で通知メールを送信する」処理を行うコードをご紹介します。
※コピペで使用できるサンプルコード付き
以前も似たような自動メール通知コードをご紹介しましたが、今回はChat GPT-4の助けもあり、さらに改良されたものとなっています。
▼前回の「仕入予定日の自動メール通知」コードに関する記事はこちら
blog.sugiokasystem.co.jp
- 請求予定日の自動メール通知 GASコード
- 初期設定
- GASコードで「請求予定日の自動メール通知処理」を行う
- GASコード実行トリガーの設定
- 杉岡システム株式会社
コード処理の流れと主な内容
今回ご紹介するGoogle Apps Script(GAS)のコード「Googleスプレッドシートのデータと連動させて請求予定日の自動メール通知を行う」処理の流れと主な内容は以下の通りです。
- スプレッドシートの設定
・GASと連携させたいスプレッドシートのIDとシート名を指定。- データの取得
・上記で指定したシートから、すべてのデータを取得。
・シートのヘッダー名を取得。
・シートのヘッダー名を使用して、各カラム名に対応するインデックスを取得。- 今日の日付の取得
・通知を送るかどうかの基準として、今日の日付を取得。- メール送信先および送信タイミングの設定とメール件名の作成
・メール送信先の設定。
・メール送信タイミングの設定とメールの件名を作成。
①請求予定日を確認。
②今日と請求予定日との間の日数差を計算。
③日数差に応じてメールの件名を生成し、メール送信タイミングを設定。- メール本文の作成
・メールの本文を作成。
・送信するメールの情報を配列に保存。
- メールの送信
・上記のステップで保存したメールの情報をもとに、メールを実際に送信。
- 日付の差分を計算する補助関数
・2つの日付の間の日数差を計算する関数を定義し、上記の「4. メール送信先と送信タイミングの設定」ステップで利用。
GASコード作成・編集方法
GASで使われているコードの言語は「JavaScript」に近いです。
コードの編集はGoogle Apps Scriptで直接組むことをオススメします。
デバッグ機能もあるので、操作性は高いです。
www.google.com
初期設定
GoogleスプレッドシートとGASの連携
以下の手順でスプレッドシートとGoogle Apps Scriptを連携させます。
- 設定したいスプレッドシートを開く。
- スプレッドシート上メニュー「拡張機能」>「Apps Script」を選択。
- Google Apps Scriptのプロジェクト編集画面(コード編集画面)に遷移。
- プロジェクト編集画面上部の「無題のプロジェクト」部分を編集して、適当なプロジェクトタイトルを設定。
以上で、設定したいスプレッドシートとGoogle Apps Scriptの連携設定は完了です。
続いて、スプレッドシートと今回作成したGASプロジェクトの「タイムゾーン」を設定していきます。
タイムゾーンの設定
スプレッドシートとGAS、それぞれのタイムゾーンが異なっている場合、コードを組んでいく上で時間の計算に狂いが生じる可能性があります。先にタイムゾーンを統一しておきましょう。
スプレッドシートのタイムゾーンを設定する
スプレッドシート上メニュー「ファイル」 > 「設定」を選択。
「このスプレッドシートの設定」画面が開いたら、「全般」タブ内のタイムゾーンを「(GMT+09:00) Tokyo」に設定。(日本時間を使用する場合)
右下の「設定を保存」ボタンをクリックして設定を保存。
Google Apps Script(GAS)のタイムゾーンを設定する
- GAS(https://script.google.com/home)を開く。
- 「自分のプロジェクト」の中から設定したいプロジェクトを選択。(※プロジェクトを作成していない場合は上記「スプレッドシートとGASの連携」の手順で先にプロジェクトを作成してください。)
- 左メニューの「歯車マーク」を選択。
- 全般設定内のタイムゾーンを先程設定したスプレッドシートと同じタイムゾーンに設定。(日本時間にする場合は「(GMT+09:00) 日本標準時 - 東京」を選択。)
GASコードで「請求予定日の自動メール通知処理」を行う
各ステップごとにコードを紹介・解説していきます。
コードの変数部分を必要に応じて修正し、ご利用ください。
ステップ1.必要な定数や変数を初期化
function sendInvoiceNotifications() { // スプレッドシートのIDと対象のシートを指定 const spreadsheetId = 'スプレッドシートID'; const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName('シート名'); // シートのデータを取得 const data = sheet.getDataRange().getValues(); // ヘッダー情報を取得し、カラム名からインデックスを取得する関数を定義 const headers = data[0]; // シート1行目をヘッダーとして取得 const getColIndex = (name) => headers.indexOf(name); // 現在の日付と時間を表す新しいDateオブジェクトを生成 const today = new Date(); // 後で送信するメールの情報を格納するための空の配列を定義 const emailsToSend = [];
スプレッドシートIDは、対象のスプレッドシートのURLから確認できます。
ステップ2.各カラムのインデックスを取得
スプレッドシートのカラム名(ヘッダー名)からインデックスを取得する方式
サンプルコードでは、スプレッドシートのインデックス(データ)取得を、カラム名(ヘッダー名)に依存する方式を取っています。
const indices = { CUSTOMER_NAME: getColIndex("取引先名"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名1") ITEM_NAME: getColIndex("品名"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名2") UNIT_PRICE: getColIndex("単価(税抜)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名3") QUANTITY: getColIndex("数量"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名4") PRICE: getColIndex("小計(税抜)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名5") TOTAL_AMOUNT: getColIndex("合計(税込)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名6") INVOICE_DATE: getColIndex("請求予定日"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名7") INVOICE_NUMBER: getColIndex("請求番号"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名8") ORDER_DATE: getColIndex("受注日"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名9") ORDER_NUMBER: getColIndex("注文番号") // COLUMN_NAME: getColIndex("スプレッドシートのカラム名10") };
ステップ1のgetColIndex("")
関数を使用して、当ステップindices
内で指定したカラム名と一致するスプレッドシートのカラム名のインデックスを取得し、それをオブジェクトindices
に格納します。
ここで定義したカラム名とスプレッドシートのカラム名が一致しない場合は、getColIndex
関数は-1
を返し、その結果エラーが発生する可能性があります。
ここで定義したカラム名とスプレッドシートのカラム名が一致する場合は、スプレッドシートのカラムの順序(列の並び)を変更しても正しいインデックスが取得されるため、正常に動作します。
スプレッドシートのカラムの順序(列の並び)からインデックスを取得する方式
カラム名に左右されず、スプレッドシートのカラムの順序(列の並び)からインデックスを取得したい場合は、以下のコードをご利用ください。
ただし、スプレッドシートのカラムの順序を変更した場合、同時にコードも修正しないと、処理が正常に行われなくなるのでご注意ください。
// カラムの順序に基づくインデックスを定義 const indices = { CUSTOMER_NAME: 0, // 「取引先名」が1列目にあると仮定 ITEM_NAME: 1, // 「品名」が2列目にあると仮定 UNIT_PRICE: 2, // 「単価(税抜)」が3列目にあると仮定 QUANTITY: 3, // 「数量」が4列目にあると仮定 PRICE: 4, // 「小計(税抜)」が5列目にあると仮定 TOTAL_AMOUNT: 5, // 「合計(税込)」が6列目にあると仮定 INVOICE_DATE: 6, // 「請求予定日」が7列目にあると仮定 INVOICE_NUMBER: 7, // 「請求番号」が8列目にあると仮定 ORDER_DATE: 8, // 「受注日」が9列目にあると仮定 ORDER_NUMBER: 9 // 「注文番号」が10列目にあると仮定 };
ステップ3.メール送信先および送信タイミングの設定とメール件名の作成
// 請求予定日に基づいて通知内容を作成 for (let i = 1; i < data.length; i++) { // ヘッダー行(i=0)より後の行を取得 // 現在の行(i行目)のデータを取得 const row = data[i]; // 請求予定日を取得 const invoiceDate = new Date(row[indices.INVOICE_DATE]); // 日付が不正であれば、次の行に移動 if (!invoiceDate) continue; // 現在の日付と請求予定日との差を計算 const daysDifference = dateDiffInDays(today, invoiceDate); // メイン受信者とCC受信者のメールアドレスを設定 const mainRecipients = ['YOUR_MAIN_EMAIL1', 'YOUR_MAIN_EMAIL2']; const ccRecipients = ['YOUR_CC_EMAIL1', 'YOUR_CC_EMAIL2']; // ccが要らない場合は削除 // 取引先の名前を取得 const customerName = row[indices.CUSTOMER_NAME]; let subject; // 残りの日数にもとづいてメールの件名を変更し、メールを送信 switch (daysDifference) { case 7: // 請求予定日まであと7日 case 3: // 請求予定日まであと3日 case 1: // 請求予定日まであと1日 case 0: // 請求予定日当日 subject = `${customerName}様への請求予定日まであと${daysDifference}日`; // 請求予定日が当日の場合、件名を更新 if (daysDifference === 0) subject = `${customerName}様への請求予定日当日です`; break; default: // 上記のいずれのケースも該当しない場合、次の行に移動 continue; }
subject
オブジェクトの補足
subject
オブジェクトで、請求予定日までの残り日数に応じてメール件名を作成し、同時にメール送信タイミングも設定します。(件名作成処理が実行される = メール送信処理実行)
subject
オブジェクトには、テンプレートリテラルを使用しています。
テンプレートリテラルは、JavaScriptのES6(ECMAScript 2015)で導入された文字列リテラルの新しい構文です。
バッククォート( ` ` )で囲まれた文字列の中に変数や式を埋め込むことができます。
これにより、文字列の結合や複数行の文字列を簡単に扱うことができます。
subject = `メール件名`;
◆注意点:
- テンプレートリテラルの開始と終了の記号( ` )の間の、すべてのスペースや改行は、そのまま文字列に反映されます。不要なスペースや改行を入れないように注意してください。
${}
内部で式の評価エラーが発生した場合、エラーがスロー(throw)されるので、存在しない変数や不正なアクセスを避ける必要があります。
ステップ4.メール本文の作成
// メール本文の単価、小計、合計金額を日本円形式にフォーマット const unitPrice = `${Number(row[indices.UNIT_PRICE]).toLocaleString('ja-JP')}円`; const price = `${Number(row[indices.PRICE]).toLocaleString('ja-JP')}円`; const formattedTotalAmount = `${Number(row[indices.TOTAL_AMOUNT]).toLocaleString('ja-JP')}円`; // メール本文の日付を「yyyy年m月d日」形式にフォーマット const options = { year: 'numeric', month: 'long', day: 'numeric' }; // メールの本文を作成 const body = `取引先名: ${row[indices.CUSTOMER_NAME]} 受注日: ${row[indices.ORDER_DATE].toLocaleDateString('ja-JP', options)} 注文番号: ${row[indices.ORDER_NUMBER]} 請求予定日: ${row[indices.INVOICE_DATE].toLocaleDateString('ja-JP', options)} 請求番号: ${row[indices.INVOICE_NUMBER]} 品名: ${row[indices.ITEM_NAME]} 単価(税抜): ${unitPrice} 数量: ${row[indices.QUANTITY]} 小計(税抜): ${price} 合計(税込): ${formattedTotalAmount} 請求予定表を見る: スプレッドシートURL`; // 送信するメールの情報を保存 emailsToSend.push({ to: mainRecipients.join(', '), cc: ccRecipients.join(', '), // ccが要らない場合は削除 subject, body }); }
unitPrice
price
formattedTotalAmount
body
オブジェクトの補足
メール本文作成用body
オブジェクトおよび、メール本文で使用する金額の形式をフォーマットするunitPrice
price
formattedTotalAmount
オブジェクトには、「ステップ3」のsubject
オブジェクトと同じく、テンプレートリテラルを使用しています。
// メールの本文を作成 const body = ` メール本文 `;
テンプレートリテラル内では、改行やスペースの処理を直接文字列内で行うことができ、テンプレートリテラル内で行った改行やスペースは、実際のメール本文にそのまま反映されます。
メール内でのリンクや参考URLなどの情報も、文字列内に埋め込むことができます。
◆注意点:
- テンプレートリテラルの開始と終了の記号( ` )の間の、すべてのスペースや改行は、そのまま文字列に反映されます。不要なスペースや改行を入れないように注意してください。
${}
内部で式の評価エラーが発生した場合、エラーがスロー(throw)されるので、存在しない変数や不正なアクセスを避ける必要があります。
options
オブジェクトの補足
サンプルコードでは、下記の通りoptions
オブジェクトで「yyyy年m月d日」表示になるよう指定しています。
// メール本文の日付を「yyyy年m月d日」形式にフォーマット const options = { year: 'numeric', month: 'long', day: 'numeric' };
options
オブジェクト内で指定した形式を変更することで、いくつものパターンに変更可能です。具体例は以下の通りです。
const date = new Date(2023, 8, 8); // 2023年9月8日 // 「yy年m月dd日」表示 - Year 2-digit, Month 2-digit, Day 2-digit const options1 = { year: '2-digit', month: '2-digit', day: '2-digit' }; console.log(date.toLocaleDateString('ja-JP', options1)); // → 23/09/08 // 「yyyy年m月d日(w)」表示 - Weekday short, Year numeric, Month long, Day numeric const options2 = { weekday: 'short', year: 'numeric', month: 'long', day: 'numeric' }; console.log(date.toLocaleDateString('ja-JP', options2)); // → 2023年9月8日(金)
その他toLocaleDateString()
メソッドに関する詳細は、下記サイトをご覧ください。
developer.mozilla.org
js.studio-kingdom.com
ステップ5.用意したメールリストを使って、メールを送信
// メールを送信 emailsToSend.forEach(email => { MailApp.sendEmail({ to: email.to, cc: email.cc, // ccが要らない場合は削除 subject: email.subject, body: email.body, name: 'Your Sender Name' // 送信者名の設定 }); }); }
ステップ6.日付の差を計算する補助関数
/** * 2つの日付間の差を日数で計算する関数 * * @param {Date} a - 開始日 * @param {Date} b - 終了日 * @returns {number} - 日付の差分の日数 */ function dateDiffInDays(a, b) { // 1日のミリ秒数を定義 (1000ミリ秒 x 60秒 x 60分 x 24時間) const _MS_PER_DAY = 1000 * 60 * 60 * 24; // 入力された日付aをUTC(協定世界時)に変換。時、分、秒は考慮せず、日付のみを基に計算 const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate()); // 入力された日付bをUTC(協定世界時)に変換。時、分、秒は考慮せず、日付のみを基に計算 const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()); // 2つのUTC時間の差を計算し、ミリ秒単位での差を日単位に変換。小数点以下は切り捨て return Math.floor((utc2 - utc1) / _MS_PER_DAY); }
サンプルコード全体
コードの変数部分を必要に応じて修正し、ご利用ください。
function sendInvoiceNotifications() { // スプレッドシートのIDと対象のシートを指定 const spreadsheetId = 'スプレッドシートID'; const sheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName('シート名'); // シートのデータを取得 const data = sheet.getDataRange().getValues(); // ヘッダー情報を取得し、カラム名からインデックスを取得する関数を定義 const headers = data[0]; // シート1行目をヘッダーとして取得 const getColIndex = (name) => headers.indexOf(name); // 現在の日付と時間を表す新しいDateオブジェクトを生成 const today = new Date(); // 後で送信するメールの情報を格納するための空の配列を定義 const emailsToSend = []; const indices = { CUSTOMER_NAME: getColIndex("取引先名"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名1") ITEM_NAME: getColIndex("品名"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名2") UNIT_PRICE: getColIndex("単価(税抜)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名3") QUANTITY: getColIndex("数量"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名4") PRICE: getColIndex("小計(税抜)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名5") TOTAL_AMOUNT: getColIndex("合計(税込)"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名6") INVOICE_DATE: getColIndex("請求予定日"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名7") INVOICE_NUMBER: getColIndex("請求番号"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名8") ORDER_DATE: getColIndex("受注日"), // COLUMN_NAME: getColIndex("スプレッドシートのカラム名9") ORDER_NUMBER: getColIndex("注文番号") // COLUMN_NAME: getColIndex("スプレッドシートのカラム名10") }; // 請求予定日に基づいて通知内容を作成 for (let i = 1; i < data.length; i++) { // ヘッダー行(i=0)より後の行を取得 // 現在の行(i行目)のデータを取得 const row = data[i]; // 請求予定日を取得 const invoiceDate = new Date(row[indices.INVOICE_DATE]); // 日付が不正であれば、次の行に移動 if (!invoiceDate) continue; // 現在の日付と請求予定日との差を計算 const daysDifference = dateDiffInDays(today, invoiceDate); // メイン受信者とCC受信者のメールアドレスを設定 const mainRecipients = ['YOUR_MAIN_EMAIL1', 'YOUR_MAIN_EMAIL2']; const ccRecipients = ['YOUR_CC_EMAIL1', 'YOUR_CC_EMAIL2']; // ccが要らない場合は削除 // 取引先の名前を取得 const customerName = row[indices.CUSTOMER_NAME]; let subject; // 残りの日数にもとづいてメールの件名を変更し、メールを送信 switch (daysDifference) { case 7: // 請求予定日まであと7日 case 3: // 請求予定日まであと3日 case 1: // 請求予定日まであと1日 case 0: // 請求予定日当日 subject = `${customerName}様への請求予定日まであと${daysDifference}日`; // 請求予定日が当日の場合、件名を更新 if (daysDifference === 0) subject = `${customerName}様への請求予定日当日です`; break; default: // 上記のいずれのケースも該当しない場合、次の行に移動 continue; } // メール本文の単価、小計、合計金額を日本円形式にフォーマット const unitPrice = `${Number(row[indices.UNIT_PRICE]).toLocaleString('ja-JP')}円`; const price = `${Number(row[indices.PRICE]).toLocaleString('ja-JP')}円`; const formattedTotalAmount = `${Number(row[indices.TOTAL_AMOUNT]).toLocaleString('ja-JP')}円`; // メール本文の日付を「yyyy年m月d日」形式にフォーマット const options = { year: 'numeric', month: 'long', day: 'numeric' }; // メールの本文を作成 const body = `取引先名: ${row[indices.CUSTOMER_NAME]} 受注日: ${row[indices.ORDER_DATE].toLocaleDateString('ja-JP', options)} 注文番号: ${row[indices.ORDER_NUMBER]} 請求予定日: ${row[indices.INVOICE_DATE].toLocaleDateString('ja-JP', options)} 請求番号: ${row[indices.INVOICE_NUMBER]} 品名: ${row[indices.ITEM_NAME]} 単価(税抜): ${unitPrice} 数量: ${row[indices.QUANTITY]} 小計(税抜): ${price} 合計(税込): ${formattedTotalAmount} 請求予定表を見る: スプレッドシートURL`; // 送信するメールの情報を保存 emailsToSend.push({ to: mainRecipients.join(', '), cc: ccRecipients.join(', '), // ccが要らない場合は削除 subject, body }); } // メールを送信 emailsToSend.forEach(email => { MailApp.sendEmail({ to: email.to, cc: email.cc, // ccが要らない場合は削除 subject: email.subject, body: email.body, name: 'Your Sender Name' // 送信者名の設定 }); }); } /** * 2つの日付間の差を日数で計算する関数 * * @param {Date} a - 開始日 * @param {Date} b - 終了日 * @returns {number} - 日付の差分の日数 */ function dateDiffInDays(a, b) { // 1日のミリ秒数を定義 (1000ミリ秒 x 60秒 x 60分 x 24時間) const _MS_PER_DAY = 1000 * 60 * 60 * 24; // 入力された日付aをUTC(協定世界時)に変換。時、分、秒は考慮せず、日付のみを基に計算 const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate()); // 入力された日付bをUTC(協定世界時)に変換。時、分、秒は考慮せず、日付のみを基に計算 const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate()); // 2つのUTC時間の差を計算し、ミリ秒単位での差を日単位に変換。小数点以下は切り捨て return Math.floor((utc2 - utc1) / _MS_PER_DAY); }
GASコード実行トリガーの設定
GAS編集画面の左メニュー「トリガー」を選択して、スクリプトコードを実行するタイミングを設定します。
「毎朝9時頃にコードを実行する」設定にする場合は、
- トリガー設定画面の右下「トリガーを追加」をクリック
- 実行する関数を選択で「sendInvoiceNotifications」(実行したいコードの関数名)を選択
- 実行するデプロイを選択で「Head」を選択(ここで特定のバージョンを選ぶことも可能)
- イベントのソースを選択で「時間主導型」を選択
- 時間ベースのトリガーのタイプを選択で「日付ベースのタイマー」を選択
- 時刻を選択で「午前8時~9時」を選択
- 「保存」をクリック
と設定することで、「毎朝8時~9時の間にコードを実行し、コードで設定した条件に該当した時、予定通知メールが送信される」ようになります。
以上で、GASとGoogleスプレッドシートを連携させた予定日通知メールの自動送信設定の完了です。
杉岡システム株式会社
電子機器(ハード・ソフト)の開発・製造は、杉岡システムにお任せください。
自社製品「映像遅延再生装置」および「映像遅延再生メモリ」の販売も行っています。
営業時間
平日10:00 - 17:30(土日祝休み)
住所
〒573-0046
大阪府枚方市宮之下町8番2号(ABCセンター 西棟3階)
杉岡システム株式会社
連絡先
- TEL : 072-853-3553(平日10:00 - 17:30)
- FAX : 072-853-3577(平日10:00 - 17:30)
- MAIL : support@sugiokasystem.co.jp
- WEB:https://sugiokasystem.co.jp
※営業時間外にお問い合わせいただいた場合は、翌営業日以降に対応いたします。