今回は、Google スプレッドシートと Google Apps
Script(GAS)を連携させて「取引先への請求予定日(請求期限の目安)が近づくと、自動で通知メールを送信する」処理を行うコードを紹介します。
※コピペで使用できるサンプルコード付き
コード処理の流れと主な内容
今回紹介する GAS のコードは、設定(config.js)・ユーティリティ(utils.js)・メイン処理(main.js)の3ファイルを中心に構成しています。
メイン処理はパイプライン形式(データ取得 → グループ化 → メール生成 → 送信)で、処理の流れと主な内容は以下の通りです。
-
設定値の定義(config.js) — スプレッドシートID・シート名・メール送信先・通知タイミング(残り日数)・カラム名マッピングを一箇所に集約。
-
ユーティリティ関数の定義(utils.js) — 日付の差分計算、日本円フォーマット、端数処理(
roundAmount)、日付フォーマット、ヘッダー名からカラムインデックスを構築する関数を用意。 -
スプレッドシートのデータ取得(main.js) — config.js で指定したスプレッドシートのシートから全データを取得し、ヘッダー行からカラムインデックスを構築。
-
通知対象のグループ化(main.js) — 各行の請求予定日と今日の日付の差を計算し、config.js で指定した通知日数(7日前・3日前・1日前・当日)に該当する行を残り日数でグループ化。さらに同じ請求予定日内で取引先名ごとにまとめる。
-
メール件名・本文の作成(main.js) — 同じ請求予定日の取引先を1通のメールにまとめ、取引先ごとのセクションに明細と税率別の請求合計を表示する件名・本文を作成。
-
メールの送信(main.js) — 作成したメール情報をもとに、config.js で設定した送信先へメールを送信。
GAS のコード作成・編集方法
GAS のコードは JavaScript(V8 エンジン)をベースとしており、一般的な JavaScript の文法で記述できます(参考: V8 ランタイムの概要 — Apps Script ドキュメント)。
コードの編集は
clasp(Command Line Apps Script Projects)
を使い、ローカル環境で行うことをおすすめします。 ローカルで編集したコードを Git で管理し、clasp push
で GAS
プロジェクトに反映するワークフローにより、バージョン管理・差分確認・チーム共有が容易になります。
デプロイには、Git 情報を自動付与するデプロイスクリプト(deploy.sh)を用意しておくと、Apps Script
のデプロイ履歴からどのコミットに対応するコードかを特定でき、運用時のトラブルシューティングに役立ちます。
※ GAS エディタ(script.google.com)はデバッグやログ確認に役立つため、必要に応じて併用します。
プロジェクト構成のポイント
今回のサンプルコードは、以下のファイル構成になっています。
invoice-notification/
├── config.js ← 設定値(ID・メールアドレス・通知日数・トリガー定義など)
├── utils.js ← 汎用ユーティリティ(日付計算・金額フォーマット)
├── main.js ← メイン処理(データ取得 → グループ化 → メール送信)
├── menu.js ← カスタムメニュー(スプレッドシート上の操作UI)
├── triggers.js ← トリガー設定(自動実行スケジュール管理)
├── appsscript.json ← GAS マニフェスト
└── .clasp.json ← clasp 設定(Script ID)
この構成の利点は次の通りです。
-
設定変更が簡単 — メール送信先や通知タイミングを変えたいとき、
config.jsだけを修正すればよい -
再利用しやすい —
utils.jsの関数(日付計算・金額フォーマット等)は他の GAS プロジェクトでもそのまま使える -
見通しがよい —
main.jsに通知送信などの主処理を集約し、設定や共通処理を分離することで、処理の流れを把握しやすい -
運用しやすい —
menu.jsでスプレッドシートから手動送信やトリガー管理ができ、triggers.jsでトリガー設定をコードで管理できる
GAS では同一プロジェクト内の
.js ファイルがすべてグローバルスコープを共有するため、import
/ export なしでファイル間の関数や変数を参照できます。
完全なコードは GitHub リポジトリで公開しています。
初期設定
前提
本記事は次の環境を前提とします。
- Google アカウント(GAS プロジェクトを作成・所有できる)
- Node.js v20.x 以降
@google/claspv3.x 以降
Google スプレッドシートと GAS の連携
以下の手順でスプレッドシートと GAS を連携させます。
- 設定したいスプレッドシートを開く。
- スプレッドシート上メニュー「拡張機能」>「Apps Script」を選択。
- GAS エディタ(コード編集画面)に遷移。
- GAS エディタ上部の「無題のプロジェクト」部分を編集して、適当なプロジェクトタイトルを設定。
以上で、スプレッドシートと GAS の連携設定は完了です。 続いて、スプレッドシートと今回作成した GAS プロジェクトの「タイムゾーン」を設定していきます。
タイムゾーンの設定
スプレッドシートと GAS、それぞれのタイムゾーンが異なっている場合、コードを組んでいく上で時間の計算に狂いが生じる可能性があります。先にタイムゾーンを統一しておきましょう。
スプレッドシートのタイムゾーンを設定する
スプレッドシート上メニュー「ファイル」 > 「設定」を選択。 「このスプレッドシートの設定」画面が開いたら、「全般」タブ内のタイムゾーンを「(GMT+09:00) Tokyo」に設定します(日本時間を使用する場合)。 右下の「設定を保存」ボタンをクリックして設定を保存します。
GAS のタイムゾーンを設定する
- GAS(https://script.google.com/home)を開く。
- 「自分のプロジェクト」の中から設定したいプロジェクトを選択。(※プロジェクトを作成していない場合は上記「Google スプレッドシートと GAS の連携」の手順で先にプロジェクトを作成してください。)
- 左メニューの「歯車マーク」を選択。
- 全般設定内のタイムゾーンを先程設定したスプレッドシートと同じタイムゾーンに設定。(日本時間にする場合は「(GMT+09:00) 日本標準時 - 東京」を選択。)
GAS のコードで「請求予定日の自動メール通知処理」を行う
各ファイルごとにコードを紹介・解説していきます。 コードの変数部分を必要に応じて修正し、利用してください。
config.js — 設定の一元管理
const CONFIG = {
// ─── スプレッドシート ───
SPREADSHEET_ID: 'スプレッドシートID',
SHEET_NAME: 'シート名',
SPREADSHEET_URL: 'https://docs.google.com/spreadsheets/d/スプレッドシートID/edit',
// ─── メール ───
SENDER_NAME: 'Your Sender Name',
MAIN_RECIPIENTS: ['YOUR_MAIN_EMAIL1', 'YOUR_MAIN_EMAIL2'],
CC_RECIPIENTS: ['YOUR_CC_EMAIL1', 'YOUR_CC_EMAIL2'], // CC不要なら空配列 [] に
// ─── 通知タイミング(請求予定日までの残り日数) ───
NOTIFICATION_DAYS: [7, 3, 1, 0],
// ─── カラム名マッピング(スプレッドシートのヘッダー名) ───
COLUMNS: {
CUSTOMER_NAME: '取引先名',
ORDER_DATE: '受注日',
ORDER_NUMBER: '注文番号',
ITEM_NAME: '品名',
UNIT_PRICE: '単価(税抜)',
QUANTITY: '数量',
PRICE: '小計(税抜)',
TAX: '税率',
INVOICE_DATE: '請求予定日',
INVOICE_NUMBER: '請求番号',
},
};
/**
* トリガー定義(配列)
* setupTrigger / setupTriggerHeadless はこの定義に基づいてトリガーを作成する。
* 複数トリガーを管理したい場合はオブジェクトを追加してください。
*/
var TRIGGER_CONFIGS = [
{
functionName: 'sendInvoiceNotifications',
label: '請求予定日メール通知',
type: 'daily',
hour: 8
},
{
functionName: 'sendInvoiceNotifications',
label: '請求予定日メール通知(週次)',
type: 'weekly',
hour: 15,
weekDay: ScriptApp.WeekDay.MONDAY
}
];
スプレッドシートIDは、対象のスプレッドシートのURLから確認できます。
docs.google.com/spreadsheets/d/スプレッドシートID/edit#gid=シートID
カラム名マッピングについて
COLUMNS
にはスプレッドシートのヘッダー名(1行目)をそのまま記述します。後述の
buildColumnIndices()
関数がヘッダー行と照合してカラムのインデックス(列番号)を自動取得するため、スプレッドシートの列の並び順を変更しても正常に動作します。
ここで定義したカラム名とスプレッドシートのヘッダー名が一致しない場合、コンソールに警告が表示され、該当カラムのインデックスは
-1 になります。
カラムの順序(列の並び)からインデックスを取得する方式
ヘッダー名に依存せず、列の並び順からインデックスを直接指定することもできます。ただし、スプレッドシートの列を並び替えた場合はコードも同時に修正する必要があります。
// config.js 内の COLUMNS を以下に差し替え
COLUMNS: {
CUSTOMER_NAME: 0, // 「取引先名」が1列目にあると仮定
ORDER_DATE: 1, // 「受注日」が2列目にあると仮定
ORDER_NUMBER: 2, // 「注文番号」が3列目にあると仮定
ITEM_NAME: 3, // 「品名」が4列目にあると仮定
UNIT_PRICE: 4, // 「単価(税抜)」が5列目にあると仮定
QUANTITY: 5, // 「数量」が6列目にあると仮定
PRICE: 6, // 「小計(税抜)」が7列目にあると仮定
TAX: 7, // 「税率」が8列目にあると仮定
INVOICE_DATE: 8, // 「請求予定日」が9列目にあると仮定
INVOICE_NUMBER: 9, // 「請求番号」が10列目にあると仮定
},
この方式の場合、main.js では buildColumnIndices() の代わりに
CONFIG.COLUMNS を直接使用します。
utils.js — ユーティリティ関数
/**
* 2つの日付間の差を日数で計算する
*/
function dateDiffInDays(a, b) {
const _MS_PER_DAY = 1000 * 60 * 60 * 24;
const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return Math.floor((utc2 - utc1) / _MS_PER_DAY);
}
/**
* 数値を日本円形式にフォーマットする(例: 1234 → "1,234円")
*/
function formatJPY(amount) {
return `${Number(amount).toLocaleString('ja-JP')}円`;
}
/**
* 金額の端数処理(デフォルト: 切り捨て)
*
* 切り上げにする場合: return Math.ceil(amount);
* 四捨五入にする場合: return Math.round(amount);
*/
function roundAmount(amount) {
return Math.floor(amount);
}
/**
* Date を「yyyy年m月d日」形式にフォーマットする
*/
function formatDateJP(date) {
const options = { year: 'numeric', month: 'long', day: 'numeric' };
return date.toLocaleDateString('ja-JP', options);
}
/**
* ヘッダー行とカラム名マッピングからインデックスを構築する
*/
function buildColumnIndices(headers, columnMap) {
const indices = {};
for (const [key, headerName] of Object.entries(columnMap)) {
const index = headers.indexOf(headerName);
if (index === -1) {
console.warn(`カラム "${headerName}"(キー: ${key})がヘッダー行に見つかりません`);
}
indices[key] = index;
}
return indices;
}
dateDiffInDays — 日付差分の計算
2つの日付をUTC(協定世界時)に変換し、時・分・秒を除いた純粋な日数差を返します。Date.UTC()
で変換することで、タイムゾーンの影響を排除して正確な日数差が得られます。
roundAmount — 端数処理
消費税額の端数処理に使用します。デフォルトは
Math.floor()(切り捨て)です。切り上げにしたい場合は
Math.ceil()、四捨五入にしたい場合は
Math.round() に変更してください。
formatDateJP — 日付フォーマット
toLocaleDateString() メソッドに
options オブジェクトを渡して表示形式を指定しています。options
の組み合わせで様々な形式に変更可能です。
const date = new Date(2023, 8, 8); // 2023年9月8日
// 「yy年m月dd日」表示
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)」表示
const options2 = { weekday: 'short', year: 'numeric', month: 'long', day: 'numeric' };
console.log(date.toLocaleDateString('ja-JP', options2)); // → 2023年9月8日(金)
その他
toLocaleDateString()
メソッドに関する詳細は、下記サイトを参照してください。
buildColumnIndices — カラムインデックス構築
config.js の
COLUMNS(カラム名マッピング)とシートのヘッダー行を照合し、{ キー名: 列番号 }
のオブジェクトを返します。これにより main.js では
col.CUSTOMER_NAME
のようにキー名でデータにアクセスできます。ヘッダー名が一致しないカラムがある場合は
console.warn で警告を出力します。
main.js — メイン処理
main.js はパイプライン形式(データ取得 → グループ化 → メール生成 → 送信)で処理を行います。ここでは主要な3つの関数を紹介します。
※ GAS では関数名の末尾に _(アンダースコア)を付けると、GAS
エディタの関数一覧やカスタムメニューの候補に表示されなくなります。外部から直接呼び出さないヘルパー関数には
_ を付けるのが慣習です。
エントリポイント — sendInvoiceNotifications()
トリガーやカスタムメニューから呼び出されるメイン関数です。パイプライン全体を制御します。
function sendInvoiceNotifications() {
// データを取得
var sheetData = getSheetData_();
if (!sheetData) {
var msg = '通知対象のデータがありません';
Logger.log(msg);
return msg;
}
var rows = sheetData.rows;
var col = sheetData.col;
var today = new Date();
// 通知対象の行を残り日数でグループ化
var groups = groupByDaysDiff_(rows, col, today);
var daysDiffKeys = Object.keys(groups);
if (daysDiffKeys.length === 0) {
var msg = '本日の通知対象はありません';
Logger.log(msg);
return msg;
}
// グループごとにメールを生成・送信
var to = CONFIG.MAIN_RECIPIENTS.join(', ');
var cc = CONFIG.CC_RECIPIENTS.join(', ');
var totalRows = 0;
for (var i = 0; i < daysDiffKeys.length; i++) {
var daysDiff = Number(daysDiffKeys[i]);
var groupRows = groups[daysDiff];
var email = buildGroupedEmailContent_(groupRows, col);
MailApp.sendEmail({
to: to,
cc: cc,
subject: email.subject,
body: email.body,
name: CONFIG.SENDER_NAME
});
totalRows += groupRows.length;
}
var msg = '請求予定通知: ' + daysDiffKeys.length + '通(' + totalRows + '件)送信しました';
Logger.log(msg);
return msg;
}
処理の流れ:
getSheetData_()でスプレッドシートからデータを取得-
groupByDaysDiff_()で通知対象の行を残り日数ごとにグループ化 -
グループごとに
buildGroupedEmailContent_()でメール件名・本文を生成 MailApp.sendEmail()でメールを送信
通知対象のグループ化 — groupByDaysDiff_()
各行の請求予定日までの残り日数を計算し、CONFIG.NOTIFICATION_DAYS(7日前・3日前・1日前・当日)に該当する行を残り日数ごとにグループ化します。
function groupByDaysDiff_(rows, col, today) {
var groups = {};
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var invoiceDate = new Date(row[col.INVOICE_DATE]);
if (isNaN(invoiceDate.getTime())) continue;
var daysDiff = dateDiffInDays(today, invoiceDate);
if (!CONFIG.NOTIFICATION_DAYS.includes(daysDiff)) continue;
if (!groups[daysDiff]) {
groups[daysDiff] = [];
}
groups[daysDiff].push(row);
}
return groups;
}
戻り値は
{ 残り日数: [行データ, ...], ... }
の形式です。例えば残り3日の行が2件、当日の行が1件なら
{ 3: [row1, row2], 0: [row3] } のようになります。
メール本文の生成 — buildGroupedEmailContent_()
同じ請求予定日のデータをさらに取引先名でグループ化し、取引先ごとのセクションに明細と税率別の請求合計を表示するメール本文を生成します。
function buildGroupedEmailContent_(groupRows, col) {
var invoiceDate = new Date(groupRows[0][col.INVOICE_DATE]);
var invoiceDateStr = formatDateJP(invoiceDate);
var customerGroups = groupByCustomer_(groupRows, col);
var customerCount = customerGroups.length;
var totalDetailCount = groupRows.length;
var subject = '【請求予定通知】' + invoiceDateStr + '予定分(' + customerCount + '件)';
// 取引先ごとのセクションを組み立て
var sections = [];
for (var i = 0; i < customerGroups.length; i++) {
var group = customerGroups[i];
var customerRows = group.rows;
// ■ ヘッダー: 取引先名 + 請求番号(空なら省略)
var invoiceNumber = customerRows[0][col.INVOICE_NUMBER];
var header = '■ ' + group.name;
if (invoiceNumber) {
header += '(' + invoiceNumber + ')';
}
// 明細テキストを組み立て・税率ごとに税抜合計を集計
var details = [];
var taxGroups = {};
for (var j = 0; j < customerRows.length; j++) {
details.push(buildDetailText_(customerRows[j], col));
var taxRate = Number(customerRows[j][col.TAX]);
var price = Number(customerRows[j][col.PRICE]);
taxGroups[taxRate] = (taxGroups[taxRate] || 0) + price;
}
// 税率ごとに消費税額を端数処理してから合算
var sortedRates = Object.keys(taxGroups).sort(
function(a, b) { return Number(a) - Number(b); }
);
var subtotalExTax = 0;
var totalTax = 0;
var taxLines = [];
for (var k = 0; k < sortedRates.length; k++) {
var rate = sortedRates[k];
var subtotal = taxGroups[rate];
var tax = roundAmount(subtotal * Number(rate));
subtotalExTax += subtotal;
totalTax += tax;
taxLines.push(' 消費税(' + (Number(rate) * 100) + '%): ' + formatJPY(tax));
}
subtotalExTax = roundAmount(subtotalExTax);
var customerTotal = subtotalExTax + totalTax;
var detailSeparator = '\\n\\n - - - - - - - - - - - - - - -\\n\\n';
var section = header + '\\n' + details.join(detailSeparator)
+ '\\n\\n ─────────────────'
+ '\\n 請求合計(税抜): ' + formatJPY(subtotalExTax)
+ '\\n' + taxLines.join('\\n')
+ '\\n 請求合計(税込): ' + formatJPY(customerTotal);
sections.push(section);
}
var separator = '\\n\\n──────────────────────────────\\n\\n';
var summary = '対象: ' + customerCount + '件(' + totalDetailCount + '明細)';
var body = sections.join(separator) + '\\n'
+ '\\n'
+ '══════════════════════════════\\n'
+ summary + '\\n'
+ '\\n'
+ '請求予定表を見る: ' + CONFIG.SPREADSHEET_URL;
return { subject: subject, body: body };
}
この関数が行っている処理:
-
groupByCustomer_()で取引先名ごとにグループ化(出現順を維持) - 取引先ごとに明細テキスト(
buildDetailText_())を組み立て -
税率ごとに税抜合計を集計し、
roundAmount()で消費税額を端数処理 - 取引先セクション(■ ヘッダー + 明細 + 請求合計)を組み立て
- 全セクションを区切り線で結合し、件名・本文を返す
※ getSheetData_()(データ取得)、groupByCustomer_()(取引先グループ化)、buildDetailText_()(1明細行のテキスト生成)の完全なコードは GitHub リポジトリを参照してください。
通知タイミングの制御
通知対象日の判定には
CONFIG.NOTIFICATION_DAYS.includes(daysDiff) を使用しています。
// config.js で通知日数を定義
NOTIFICATION_DAYS: [7, 3, 1, 0],
// main.js で判定
if (!CONFIG.NOTIFICATION_DAYS.includes(daysDiff)) continue;
通知タイミングを変更したい場合(例: 14日前にも通知したい)は、config.js の配列に
14 を追加するだけで対応できます。
メール通知の例
同じ請求予定日の取引先は1通のメールにまとめて送信されます。同じ取引先に複数の明細がある場合は1つのセクションにまとめ、取引先ごとに税率別の請求合計を表示します。
件名:
【請求予定通知】2026年4月16日予定分(2件)
本文:
■ 株式会社サンプル(INV-2026-043)
受注日: 2026年3月1日
注文番号: ORD-2026-001
品名: 製品A
単価(税抜): 100,000円 × 1
小計(税抜): 100,000円
税率: 10%
- - - - - - - - - - - - - - -
受注日: 2026年3月1日
注文番号: ORD-2026-001
品名: 製品B
単価(税抜): 50,000円 × 2
小計(税抜): 100,000円
税率: 10%
─────────────────
請求合計(税抜): 200,000円
消費税(10%): 20,000円
請求合計(税込): 220,000円
──────────────────────────────
■ 株式会社デザイン(INV-2026-044)
受注日: 2026年3月21日
注文番号: ORD-2026-004
品名: ロゴデザイン
単価(税抜): 50,000円 × 1
小計(税抜): 50,000円
税率: 10%
─────────────────
請求合計(税抜): 50,000円
消費税(10%): 5,000円
請求合計(税込): 55,000円
══════════════════════════════
対象: 2件(3明細)
請求予定表を見る: https://docs.google.com/spreadsheets/d/xxxxx/edit
GAS のコードを実行するトリガーの設定
トリガーの管理方法はどちらか一方に統一してください。GAS エディタでの手動操作とコードによる管理を混在させると、トリガーの重複や削除漏れの原因になります。
方法1: GAS エディタから手動で設定
※ この方法を選んだ場合、triggers.js
のトリガー管理機能(カスタムメニュー・clasp run)は使用しないでください。
GAS エディタの左メニュー「トリガー」を選択して、スクリプトコードを実行するタイミングを設定します。 「毎朝9時頃にコードを実行する」設定にする場合は、以下の手順で設定します。
- トリガー設定画面の右下「トリガーを追加」をクリック
- 実行する関数を選択で「sendInvoiceNotifications」(実行したいコードの関数名)を選択
- 実行するデプロイを選択で「Head」を選択(ここで特定のバージョンを選ぶことも可能)
- イベントのソースを選択で「時間主導型」を選択
- 時間ベースのトリガーのタイプを選択で「日付ベースのタイマー」を選択
- 時刻を選択で「午前8時~9時」を選択
- 「保存」をクリック
方法2: triggers.js からコードで設定
※ この方法を選んだ場合、トリガーの設定(新規追加・変更を含む)・削除はすべてカスタムメニューまたは
clasp run から行ってください。GAS
エディタのトリガー管理画面から直接操作すると、コードで管理しているトリガーと競合する原因になります(閲覧のみであれば問題ありません)。
※ setupTrigger /
deleteTrigger は
TRIGGER_CONFIGS の
functionName
に一致するトリガーだけを操作します。異なる関数名のトリガーは一覧(showTriggerStatus)に表示されるだけで、設定・削除の対象にはなりません。
サンプルコードには
triggers.js
が含まれており、トリガーの設定・一覧・削除をコードで管理できます。config.js
の
TRIGGER_CONFIGS
でトリガーの定義(実行する関数・スケジュール種別・実行時刻)を一元管理しています。
var TRIGGER_CONFIGS = [
{
functionName: 'sendInvoiceNotifications',
label: '請求予定日メール通知',
type: 'daily',
hour: 8
},
{
functionName: 'sendInvoiceNotifications',
label: '請求予定日メール通知(週次)',
type: 'weekly',
hour: 15,
weekDay: ScriptApp.WeekDay.MONDAY
}
];
カスタムメニュー「通知管理」の項目:
| メニュー項目 | 実行される関数 | 用途 |
|---|---|---|
| 今すぐ通知を送信 | runManualNotification() |
通知を手動実行し、結果をダイアログで表示(テスト送信にも利用) |
| トリガー一覧を確認 | showTriggerStatus() |
設定済みトリガーのラベル・スケジュール・種別を一覧表示 |
| トリガーを設定 | setupTrigger() |
TRIGGER_CONFIGS のトリガーを一括作成 |
| トリガーを削除 | deleteTrigger() |
管理対象トリガーを一括削除 |
スプレッドシートを開き、カスタムメニュー「通知管理」 →「トリガーを設定」を選択すると、確認ダイアログが表示されます。「はい」を選ぶとトリガーが一括作成されます。
トリガーを削除したい場合は「トリガーを削除」、または
clasp run deleteTriggerHeadless(確認) →
clasp run deleteTriggerHeadless --params '[true]'(実行)で削除できます。
上記いずれかの設定により、TRIGGER_CONFIGS
で定義したスケジュール(毎日・毎週など)に基づいてコードが自動実行され、設定した条件に該当した時に予定通知メールが送信されるようになります。
サンプルコード全体
完全なコードは GitHub リポジトリで公開しています。リポジトリにはデプロイスクリプトや clasp の設定サンプル、テスト用のサンプルデータ(CSV)も含まれています。
GAS のトリガー管理をスクリプトで一元化する方法は、以下の記事で詳しく解説しています。