手軽に使えて、便利なスプレッドシートで在庫を管理している!という方は少なくないかと思います
ただでさえ便利に使えるスプレッドシートに、「たったひと手間」加えるだけで、さらに便利に使うことができるようになる魔法の方法をご紹介したいと思います!
そこで、この記事ではGoogle Apps Script(GAS)を使用して、スプレッドシートを活用し、在庫が不足した場合に自動的に注文書を生成し、それをPDF化してメールで送信する方法を詳しく解説します!
 こーすけ先生
こーすけ先生この記事を読めば、在庫管理と注文業務の効率を大幅に向上させるためのスキルを身につけることができます!
もし、スプレッドシートを使用して在庫管理を行い、必要数量が在庫数を上回った場合に自動的に注文書を生成&注文のメールを送信できたらめちゃくちゃ便利じゃないですか?
手動での作業を自動化することで、時間と労力を節約できるようになります
GASを極めたい方や、業務の効率化を図りたい方は、ぜひこの記事を読んください!
難しいことはGASに任せて、我々人間は楽しちゃいましょう!



当ブログでは実際に仕事でGASを扱っている私が、GASの魅力について徹底的に取り上げていきます!


【お題】発注が必要な場合、発注先に注文書をPDF化してメールを送信する



張り切って演習に取り組んでいきましょう!
事前準備



本記事は初心者の方向けに丁寧に解説しているので、全体のコードがみたい!という方はここをクリックして、ジャンプしてください





今回、こちらの「GAS練習(PDF化→メール送信)」を
使って演習します!
必要に応じてコピーして使ってください!
注文書テンプレートの準備
Googleドキュメントで注文書テンプレートを適当に作成しましょう!





この{{date}}みたいに波括弧で囲んであるのは何なの?



波括弧の部分は、「可変文字部」といって、
今回の大事なポイントはここなんだ!!!



詳しくは、以下の部分で説明するよ!
もう少し待っててね!
PDF出力場所の作成



スプレッドシートの内容をGoogleドキュメントに反映し、PDFに変換するため、その出力先を作成しておきましょう!







IDは緑色の四角で囲った部分だよ!
まずは完成形のコードを見てみる



完成形のコードはこちら!
順を追って解説していくよ!
// sendMail_簡易在庫管理表のヘッダーカラム
const COL = {
  NUM:1
  , PRODUCT_NAME:2
  , REQUIRED_QUANTITY:3
  , INVENTORY_CNT:4
  , PRICE:5
  , COMPANY:6
  , STAFF:7
  , MAIL:8
}
// sendMail_注文書のテンプレートファイル
const ORDER_FORM_TEMPLATE = DriveApp.getFileById('自分のGoogleドキュメントのIDに置き換える');
// PDF出力先
const PDF_OUTDIR = DriveApp.getFolderById('自分のPDF用のフォルダIDに置き換える');
function sendMail_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}}', 'こーすけ先生');
  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();
}



長くて心が折れそう……



解説しながら進めていくから大丈夫だよ!
スクリプトを実行すると、以下のメールが自動送信されます





このGASのコードは、スプレッドシートを使用して在庫管理を行い、必要数量が在庫数を上回った場合に自動的に注文書を生成→PDFファイル化して、メール送信するものです!便利!



一気に実務に活かせそうな内容になった!
頑張らなきゃっ…!
【STEP1】セル位置を定数化
// sendMail_簡易在庫管理表のヘッダーカラム
const COL = {
  NUM:1
  , PRODUCT_NAME:2
  , REQUIRED_QUANTITY:3
  , INVENTORY_CNT:4
  , PRICE:5
  , COMPANY:6
  , STAFF:7
  , MAIL:8
}




ふむふむ
「セルの定数化」は前に学んだので、大丈夫そうです!



「セルの定数化」はマスターすると便利なので、
ガンガン活用していこうね!





sendMail_SpreadSheetの部分は過去記事を参考にしてください!
【STEP2】Googleドキュメントを編集する
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];
}


createGDoc は、テンプレートとなるドキュメントをコピーし、特定の箇所({{}}部分)に必要なデータを挿入し、注文書を生成しています
テンプレートファイルをコピーする
 // テンプレートファイルをコピーする
  const wCopyFile = ORDER_FORM_TEMPLATE.makeCopy()
        , wCopyFileId = wCopyFile.getId()
        , wCopyDoc = DocumentApp.openById(wCopyFileId); // コピーしたファイルをGoogleドキュメントとして開く
  let wCopyDocBody = wCopyDoc.getBody(); // Googleドキュメント内の本文を取得するORDER_FORM_TEMPLATE は、以下の部分を指しています
// sendMail_注文書のテンプレートファイル
const ORDER_FORM_TEMPLATE = DriveApp.getFileById('自分のGoogleドキュメントのIDに置き換える');


「const」を使って、Googleドライブのファイルを
「ORDER_FORM_TEMPLATE」という名前の箱に
入れました!
| 変数 | 再宣言 | 再代入 | スコープ | 
|---|---|---|---|
| let | できない | できる | ブロックスコープ | 
| const | できない | できない | ブロックスコープ | 
| var | できる | できる | 関数スコープ | 



「const」は箱の中身を変えたくない時に使います!
「let」や「var」を使って定義すると、うっかり中身が
書き換わってしまう恐れがあるので…


 // テンプレートファイルをコピーする
  const wCopyFile = ORDER_FORM_TEMPLATE.makeCopy()
        , wCopyFileId = wCopyFile.getId()
        , wCopyDoc = DocumentApp.openById(wCopyFileId); // コピーしたファイルをGoogleドキュメントとして開く
  let wCopyDocBody = wCopyDoc.getBody(); // Googleドキュメント内の本文を取得する


ふむふむ!
1行ずつ、読んでいけばなんとなく何しているか分かるぞ!



時間があったら公式リファレンスに
目を通して勉強してみてね!
用意した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}}', 'こーすけ先生');
  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();ここでポイントとなるのは、冒頭にも申しあげた通り、{{XXXXXX}}で示した可変文字部です


Googleドキュメントにおいてはスプレッドシートと異なり、セル位置がないため特定の場所を更新する(スプレッドシートで例えると、「C11セル」とか)ことは不得意です



そのため、特定の場所にあらかじめ変更するための目印をつけておくことが必要になります
そのうえで、getBody()によりGoogleドキュメントのBodyを取得し、replaceTextを用いて編集します。
可変文字部を変更したGoogleドキュメントを新しいファイル名で保存する
 // ファイル名を変更する
  let fileName = rowVal[COL.COMPANY-1]+'宛注文書_'+Utilities.formatDate(new Date(), 'JST', 'yyyyMMdd');
  wCopyFile.setName(fileName);
  // コピーしたファイルIDとファイル名を返却する(あとでこのIDをもとにPDFに変換するため)
  return [wCopyFileId, fileName];この部分のコードは、新しいファイル名を作成し、ファイルの名前を変更して、その新しい名前とファイルIDを返す準備をしています!



この作業により、後続の処理でファイルを識別し、必要な操作を実行できるようになります!
【STEP3】自動作成したGoogleドキュメントをPDF化する
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();
}


この関数は 、新しく作成したGoogleドキュメントファイルを PDF ファイルに変換し、その PDF ファイルを指定したフォルダに保存する役割を果たしています
 // PDF変換するためのベースURLを作成する
  let wUrl = `https://docs.google.com/document/d/${docId}/export?exportFormat=pdf`;ここでポイントとなるのは、「https://docs.google.com/document/d/${docId}/export?exportFormat=pdf」のURLとなります
- 「https://docs.google.com/document/d/${docId}/export?exportFormat=pdf」までが対象のGoogleドキュメントを指しています
- そのあとのexport?でエクスポートを指示しており、GETパラメタへ続きます
- exportFormat=pdfは読んで字のごとく、エクスポートの形式を指示します



${docId} には、新しく作成したGoogleドキュメントファイルのIDが挿入されています!
 // headersにアクセストークンを格納する
  let wOtions = {
    headers: {
      'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
    }
  }; ScriptApp.getOAuthToken() は Google Apps Script のメソッドで、現在のスクリプトに認証されたユーザーのアクセストークンを取得します
(アクセストークンは、Google サービスへのアクセス許可を持つユーザーを識別し、認証を行います)



これにより、認証済みユーザーとして Google ドキュメントに
アクセスし、PDF ファイルを生成できるようになります
このアクセストークンを通じて、スクリプトはユーザーのアカウント権限を利用し、 Google ドキュメントにアクセスできるようになります!
Bearer認証
Bearer認証はアクセストークン認証といい、ログインID・パスワードなどでユーザ認証を行なった後に、サービスから発行されるアクセストークン(GASにおいては、スクリプト自体のアクセストークンgetOAuthToken()を使用) を受け取って、APIのリクエスト時に送信する方式です。
ヘッダー項目をAuthorization: Bearer {アクセストークン}
で設定して、HTTPリクエストを送信します
// PDFを作成する
  let wBlob = UrlFetchApp.fetch(wUrl, wOtions).getBlob().setName(fileName + '.pdf');
//PDFを指定したフォルダに保存する
  return PDF_OUTDIR.createFile(wBlob).getId();


この関数は Google ドキュメントを PDF に変換し、指定されたフォルダに保存して、その PDF の ID を取得するためのものです
実行結果





実行すると、スプレッドシート内で指定したメールアドレス宛に、
新しく作成されたPDF付のメールが送信されます!





わっ!すごい!
添付されたファイルがちゃんと僕の名前に
置き換わってる!



この方法は、過去にめちゃくちゃ苦労した末に
編み出した技なんだよね(苦笑)
まとめ
大変お疲れ様でした!
今回も「GASを用いてスプレッドシートで遊んでみよう」という試みの中で、少し複雑なことをしてみました
しかし、一つ一つを見てみるとこれまで行ったことを組み合わせただけであることに気づくかと思います



いかなるプログラムやシステムも基本的には
基礎を組み合わせて出来上がったものです!
これまで学んだGASスキルを少しずつ組み合わせて、ぜひ業務効率化を進めてみてください
次回も引き続き、スプレッドシートで遊んでいきましょう!!



X(旧:Twieer)にて、ブログの更新やQiita記事の更新、GAS情報をお届けしますので、是非フォローしてください!





おかげさまで今年5月に起業しました!
GASやGoogleサービス、プログラミング全般のご相談承ります!



YouTubeでGoogleサービスをより
快適に使う方法をご紹介しています!
見て頂けたらうれしいです






 
			






