はじめに
本シリーズでは、GASの始め方や便利な使い方、ビジネス活用まで幅広く解説します。シリーズをひと通り読んでいただければ、あなたもきっとGASマスターになれるはずです。
シリーズの対象者
- そもそもGASってなんだかわからない方
- GASを学びたいけど何から始めればいいかわからない方
- GASはわかり始めたけど、もっと活用ができないかと模索している方
- とにかくGoogleが好き! という方
前回記事
スプレッドシートで遊ぶ
では早速始めていきましょう。【0からGASを学ぶ】シリーズの第7回は「GASを用いてスプレッドシート+ドキュメント+メール送信を自動化しよう」です。今回は少し複雑なことをやってみようと思います。これまで、Googleドキュメント
やGmail
をGASから操作しました。また第5回では、スプレッドシートのデータを評価し、メールを自動送信しました。これらを組み合わせてみましょう。
今回やること
- スプレッドシート内のすべてのデータを取得し、行ごとに評価する。
- 発注が必要な場合は、注文書をGoogleドキュメントで作成する。
- GoogleドキュメントをPDF変換する。
- 変換したPDFを添付ファイルとして相手先にメールを送信する。
事前準備
今回はサンプルでいくつかファイルを作ります。もちろんサンプルなのでこの通りである必要はありませんが、これらを前提にプログラムは書かせてもらいます。
在庫管理表の準備
スプレッドシートのデータを取得しなければ始まらないので、取得元となるスプレッドシートを作りましょう。私は以下のようなスプレッドシートを作成しました。
注文書テンプレートの準備
Googleドキュメントで注文書テンプレートを適当に作成しましょう。どんな様式でも構いません。
枠で囲った箇所を可変文字部と呼ばせてもらいますが、今回のポイントはここです。
PDF出力場所の作成
「今回やること」にも記載しましたが、途中でGoogleドキュメントをPDF変換するため、その出力先を作成しておきましょう。
IDの取得
スプレッドシートはコンテナバインド型で開くため、IDは不要ですが、注文書テンプレート
とPDF出力場所
のIDは事前に取得しておきましょう。
プログラム開始
GASエディタを開く
今回も在庫管理表に対するコンテナバインド型でGASプログラムを記述していきましょう。こちらを参考にGASエディタを起動してください。では、どんどんいきますよ、ついてきてください。
結論
今回はまず最終形をお見せします。そのうえで、解説していきましょう。
// Qiita007_簡易在庫管理表のヘッダーカラム
const COL = {
NUM:1
, PRODUCT_NAME:2
, REQUIRED_QUANTITY:3
, INVENTORY_CNT:4
, PRICE:5
, COMPANY:6
, STAFF:7
, MAIL:8
}
// Qiita007_注文書のテンプレートファイル
const ORDER_FORM_TEMPLATE = DriveApp.getFileById('15ZqXoJxpuq96NaPJBbWinEYx4H-Qn2lxkRCNr2dgnKM');
// PDF出力先
const PDF_OUTDIR = DriveApp.getFolderById('1DNlhIKHlFVJ419UGh3RR3eCZkC_xyUfi');
function Qiita007_SpreadSheet() {
// コンテナバインド型なので、getActiveSpreadsheetにより自分自身を取得する
const wSpread = SpreadsheetApp.getActiveSpreadsheet()
// スプレッドシート内の1番目のシートを取得する
const wSheet = wSpread.getSheets()[0];
// シート内の全データを取得する。
// getDataRangeはデータが存在する範囲のみを取得してくれるため非常に便利
let wVals = wSheet.getDataRange().getValues();
// 今回はforEachでデータを回したいので、shiftメソッドを使って配列の最初の要素を削除する
wVals.shift();
// データを走査
wVals.forEach(function(wVal) {
// [必要数]>[在庫数]の行を特定する
if (wVal[COL.REQUIRED_QUANTITY-1]>wVal[COL.INVENTORY_CNT-1]) {
// PDF変換する元ファイルを作成する
let wFileRtn = createGDoc(wVal);
// PDF変換してファイルIDを取得する
let wPdfId = createPdf(wFileRtn[0], wFileRtn[1]);
// PDF変換したあとは元ファイルを削除する
DriveApp.getFileById(wFileRtn[0]).setTrashed(true);
// 今回はPDFファイルを添付してメールを送信する
GmailApp.sendEmail(
wVal[COL.MAIL-1]
, '【自動送信メール】発注依頼'
, `${wVal[COL.PRODUCT_NAME-1]}の在庫がなくなりました。
必要数:${wVal[COL.REQUIRED_QUANTITY-1]}
在庫数:${wVal[COL.INVENTORY_CNT-1]}`
, {attachments: DriveApp.getFileById(wPdfId).getBlob()}
)
} else {
// 何もしない
}
});
}
function createGDoc(rowVal) {
// テンプレートファイルをコピーする
const wCopyFile = ORDER_FORM_TEMPLATE.makeCopy()
, wCopyFileId = wCopyFile.getId()
, wCopyDoc = DocumentApp.openById(wCopyFileId); // コピーしたファイルをGoogleドキュメントとして開く
let wCopyDocBody = wCopyDoc.getBody(); // Googleドキュメント内の本文を取得する
// 注文書ファイル内の可変文字部(として用意していた箇所)を変更する
wCopyDocBody = wCopyDocBody.replaceText('{{date}}', Utilities.formatDate(new Date(), 'JST', 'yyyy年MM月dd日'));
wCopyDocBody = wCopyDocBody.replaceText('{{company_name}}', rowVal[COL.COMPANY-1]);
wCopyDocBody = wCopyDocBody.replaceText('{{staff_name}}', rowVal[COL.STAFF-1]);
wCopyDocBody = wCopyDocBody.replaceText('{{company_from}}', 'GAS大好きカンパニー');
wCopyDocBody = wCopyDocBody.replaceText('{{name_from}}', 'gas-suke');
wCopyDocBody = wCopyDocBody.replaceText('{{product_name}}', rowVal[COL.PRODUCT_NAME-1]);
wCopyDocBody = wCopyDocBody.replaceText('{{count}}', rowVal[COL.REQUIRED_QUANTITY-1]-rowVal[COL.INVENTORY_CNT-1]+100);
wCopyDocBody = wCopyDocBody.replaceText('{{price}}', rowVal[COL.PRICE-1]);
wCopyDocBody = wCopyDocBody.replaceText('{{total}}', rowVal[COL.PRICE-1]*(rowVal[COL.REQUIRED_QUANTITY-1]-rowVal[COL.INVENTORY_CNT-1]+100));
wCopyDoc.saveAndClose();
// ファイル名を変更する
let fileName = rowVal[COL.COMPANY-1]+'宛注文書_'+Utilities.formatDate(new Date(), 'JST', 'yyyyMMdd');
wCopyFile.setName(fileName);
// コピーしたファイルIDとファイル名を返却する(あとでこのIDをもとにPDFに変換するため)
return [wCopyFileId, fileName];
}
function createPdf(docId, fileName){
// PDF変換するためのベースURLを作成する
let wUrl = `https://docs.google.com/document/d/${docId}/export?exportFormat=pdf`;
// headersにアクセストークンを格納する
let wOtions = {
headers: {
'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
}
};
// PDFを作成する
let wBlob = UrlFetchApp.fetch(wUrl, wOtions).getBlob().setName(fileName + '.pdf');
//PDFを指定したフォルダに保存する
return PDF_OUTDIR.createFile(wBlob).getId();
}
また、添付されたファイルは以下のように出力されています。
無事に可変文字部が在庫管理表内のデータを用いて編集されていることを確認できます。
解説
Qiita007_SpreadSheet
自体にはそこまで目新しいものはないため、createGDoc
とcreatePdf
を中心に見ていきましょう。
createGDoc
function createGDoc(rowVal) {
// テンプレートファイルをコピーする
const wCopyFile = ORDER_FORM_TEMPLATE.makeCopy()
, wCopyFileId = wCopyFile.getId()
, wCopyDoc = DocumentApp.openById(wCopyFileId); // コピーしたファイルをGoogleドキュメントとして開く
let wCopyDocBody = wCopyDoc.getBody(); // Googleドキュメント内の本文を取得する
// 注文書ファイル内の可変文字部(として用意していた箇所)を変更する
wCopyDocBody = wCopyDocBody.replaceText('{{date}}', Utilities.formatDate(new Date(), 'JST', 'yyyy年MM月dd日'));
wCopyDocBody = wCopyDocBody.replaceText('{{company_name}}', rowVal[COL.COMPANY-1]);
wCopyDocBody = wCopyDocBody.replaceText('{{staff_name}}', rowVal[COL.STAFF-1]);
wCopyDocBody = wCopyDocBody.replaceText('{{company_from}}', 'GAS大好きカンパニー');
wCopyDocBody = wCopyDocBody.replaceText('{{name_from}}', 'gas-suke');
wCopyDocBody = wCopyDocBody.replaceText('{{product_name}}', rowVal[COL.PRODUCT_NAME-1]);
wCopyDocBody = wCopyDocBody.replaceText('{{count}}', rowVal[COL.REQUIRED_QUANTITY-1]-rowVal[COL.INVENTORY_CNT-1]+100);
wCopyDocBody = wCopyDocBody.replaceText('{{price}}', rowVal[COL.PRICE-1]);
wCopyDocBody = wCopyDocBody.replaceText('{{total}}', rowVal[COL.PRICE-1]*(rowVal[COL.REQUIRED_QUANTITY-1]-rowVal[COL.INVENTORY_CNT-1]+100));
wCopyDoc.saveAndClose();
// ファイル名を変更する
let fileName = rowVal[COL.COMPANY-1]+'宛注文書_'+Utilities.formatDate(new Date(), 'JST', 'yyyyMMdd');
wCopyFile.setName(fileName);
// コピーしたファイルIDとファイル名を返却する(あとでこのIDをもとにPDFに変換するため)
return [wCopyFileId, fileName];
}
ここでポイントとなるのは、冒頭にも申しあげた通り、{{XXXXXX}}
で示した可変文字部です。Googleドキュメント
においてはスプレッドシートと異なり、セル位置がないため特定の場所を更新する(例えばスプレッドシートでC11セルとか)ことは不得意です。そのため、特定の場所にあらかじめ変更するための目印をつけておくことが必要になります。
そのうえで、getBody()
によりGoogleドキュメントのBodyを取得し、replaceText
を用いて編集します。
{{}}
をお決まりのような書き方をしていますが、区切り文字はなんだってかまいません。ご自身の中で分かりやすいマークであり、明確に可変文字部とわかればそれでいいです。
createPdf
function createPdf(docId, fileName){
// PDF変換するためのベースURLを作成する
let wUrl = `https://docs.google.com/document/d/${docId}/export?exportFormat=pdf`;
// headersにアクセストークンを格納する
let wOtions = {
headers: {
'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
}
};
// PDFを作成する
let wBlob = UrlFetchApp.fetch(wUrl, wOtions).getBlob().setName(fileName + '.pdf');
//PDFを指定したフォルダに保存する
return PDF_OUTDIR.createFile(wBlob).getId();
}
ここでポイントとなるのは、https://docs.google.com/document/d/${docId}/export?exportFormat=pdf
のURLとなります。
-
https://docs.google.com/document/d/${docId}
までが対象のGoogleドキュメントを指しています。 - そのあとの
export?
でエクスポートを指示しており、GETパラメタへ続きます。 -
exportFormat=pdf
は読んで字のごとく、エクスポートの形式を指示します。
さらっとfor文をforEach文に書き換えました
let wVals = wSheet.getDataRange().getValues();
// 1行目(index:0)はヘッダー列なので、2行目からデータを走査
for (let rIdx=1; rIdx<wVals.length; rIdx++) {
// [必要数]>[在庫数]の行を特定する
if (wVals[rIdx][COL.REQUIRED_QUANTITY-1]>wVals[rIdx][COL.INVENTORY_CNT-1]) {
…
}
}
let wVals = wSheet.getDataRange().getValues();
// 今回はforEachでデータを回したいので、shiftメソッドを使って配列の最初の要素を削除する
wVals.shift();
// データを走査
wVals.forEach(function(wVal) {
// [必要数]>[在庫数]の行を特定する
if (wVal[COL.REQUIRED_QUANTITY-1]>wVal[COL.INVENTORY_CNT-1]) {
…
}
});
一見すると同様のことが行えているため、書き方のちょっとした違いのようにも見えますが、for
とforEach
はまるで異なります。
for
は構文でありいつでもどこでも使えます。しかし、forEach
は配列要素に対するメソッドであり、使用箇所は限定的となります。ただし、見ていただくとわかる通り、for
の場合に行っていたindexの管理が不要であり、配列データを扱う場合には非常に美しくプログラムを記載することができます。
forEach
でもindexを扱うことは可能です。
wVals.forEach(function(wVal, index) {
console.log(index);
}
実際には第3引数も指定できますが、この辺りの詳細はまた今度!
おわりに
お疲れ様でした。
第7回は「GASを用いてスプレッドシート+ドキュメント+メール送信を自動化しよう」ということで、少し複雑なことをしてみました。しかし、一つ一つを見てみるとこれまで行ったことを組み合わせただけであることに気づくかと思います。つまり、いかなるプログラムやシステムも基本的には基礎を組み合わせて出来上がったものです。これまで学んだGASスキルを少しずつ組み合わせて、ぜひ業務効率化を進めてみてください。次回も引き続き、スプレッドシートで遊んでいきましょう。
記事を読んで、「良いな」や「今後に期待できる!」と感じて頂けたらいいねやフォロー、コメントいただけると幸いです。それではまた次回をお楽しみに!