フォーム回答情報で履歴書を生成するシステムの開発記録。
こんにちは。新潟県湯沢町にUターンして、プログラミング教室で勤務する腰越です。本日は自動システム受注制作「第3弾」ということで、履歴書の自動生成システムへの挑戦記録を残します。
きっかけ
私は過去にGoogle Formを活用し、スマホ完結型の行政書類作成システムをつくりました。
これを知ってくれた知人から、この技を転用して「求職者からの問い合わせ→履歴書作成&提出をスマホで完結できるシステムはつくれないか」との相談を受けました。
(今のスキルではちょっと難易度が高そうだな…)とも思ったのですが、折角お声掛けいただいたので挑戦することにしました。
実装イメージと設計概要
下記動画が完成イメージです。
各ツールの準備
まず使用する各ツールの新規作成が必要ですが、ここでの注意点として必ずメインで使用するアカウントで準備を行ってください。
理由として、この後実装する
「履歴書生成」
「データ保存」
「メールの自動送信時の送信元アドレス」
は基本オーナーのアカウントで行うため、オーナー以外のアカウントで新規作成すると設定変更の手間が発生するからです。
①フォーム
履歴書生成に必要な質問項目を作成
以下機能を実装するためにメールアドレス(Googleアカウント)を収集
提出後のメール自動送信
生成した履歴書の編集権限を付与
②スプシ
指定したスプシにフォームの回答が自動記録されるよう設定
③ドキュメント
履歴書生成の際に必要なテンプレートを作成(内容は自由)
④Google Drive
コピー生成したデータを保存するためのフォルダを作成
⑤ライブラリ
挿入画像(履歴書写真)のサイズ自動調整用にimgAppというライブラリをインストール
これで準備は完了。プログラムを書いていきます!
プログラム
今回は3つの関数に分けてコードを書きました。
苦戦ポイントと合わせて記録します。
①履歴書生成
まずはプログラムから。
※コード内に出てくる「②画像挿入」「③メールの自動送信」のコード詳細は記事後半にあります
function makeDoc() {
// テンプレートとするドキュメントIDを設定
let docId = "テンプレートとするgoogleドキュメントのID"
//生成したGoogleドキュメントを保存するフォルダを取得
let folder = DriveApp.getFolderById("保存するフォルダID");
// スプシを取得
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName("シート名を入力");
// スプレッドシートの最終行・最終列を設定
let lastRow = sheet.getLastRow();
let lastColumn = sheet.getLastColumn();
// ヘッダー行(=1行目)の値を取得
let header = sheet.getRange(1, 1, lastRow, lastColumn).getDisplayValues();
//最終行(=最新の回答)を取得
let datalist = sheet.getRange(lastRow, 1, lastRow, lastColumn).getValues();
//「yyyy/mm/dd」の部分を「年月日」に変換したい場合のみ使用
datalist[0][0] = Utilities.formatDate(datalist[0][0], 'Asia/Tokyo', 'yyyy年MM月dd日'); //申込日
datalist[0][6] = Utilities.formatDate(datalist[0][6], 'Asia/Tokyo', 'yyyy年MM月dd日'); //生年月日
// テンプレートとするドキュメントをコピーしてファイル名、保存先のフォルダを設定する
// 今回はファイル名を「氏名_履歴書」となるように設定
let docCopyID = DriveApp.getFileById(docId).makeCopy(datalist[0][4] + '_履歴書', folder).getId();
//生成したコピードキュメントの編集権限を、指定アカウント(今回はフォーム回答者)に付与
DriveApp.getFileById(docCopyID).addEditor(datalist[0][1]);
//コピーしたドキュメントを取得
let doc = DocumentApp.openById(docCopyID);
let body = doc.getBody();
//スプシの{ヘッダー文字列} → フォーム回答の文字列に置換
for (let i = 0; i < header[0].length; i++) {
body.replaceText('{' + header[0][i] + '}', datalist[0][i]);
}
//②画像挿入の関数を呼び出す
appendImage(ss, sheet, lastRow, lastColumn, header, datalist, docCopyID);
//GAS操作時はsaveコマンドで変更内容を反映させる
doc.saveAndClose();
//③メールの自動送信の関数を呼び出す
mail(datalist, docCopyID)
}
上記設定で私が苦戦した点は、
「スプシの{ヘッダー文字列} → フォーム回答の文字列に置換」する部分です。replaceTextメソッドを使い、履歴書テンプレに入力しておいた{ヘッダーの文字列}部分を申込者の回答した文字列に置換しようと思ったのですが、なぜか置換できない箇所が出てしまう現象が発生してしまいました。
//(参考)replaceText()の使い方
replaceText('検索する文字列', '置換したい文字列')
原因特定に時間がかかったのですが、置換されない項目の共通点として「ヘッダーの文字列に"( )"が入っていると置換されない」ということを見つけました。
今回のシステムはツールの準備段階でフォームの質問見出しとスプシのヘッダーが連携するようになっているので、問題解消策としてフォームの質問見出しから"( )"を排除し、スプシのヘッダー文字列に"( )"をいれないようにしました。
②画像挿入
サイズ調整&docの表に挿入するためのプログラムです。
function appendImage(ss, sheet, lastRow, lastColumn, header, datalist, docCopyID) {
//Google Driveに保存された画像ファイルのID部分のみを取得
// → https://drive.google.com/open?id=◯◯の「◯◯」部分
//splitメソッドを使い、"id="の部分で分割する
let fileID = datalist[0][2].split("id=");
//IDの確認用
Logger.log(fileID[1]);
// imgAppライブラリのdoResizeを使用し、幅を160pxに変更
let image = ImgApp.doResize(fileID[1], 160);
// 新しく作成した画像ファイルをフォルダに保存
// 今回はファイル名を「氏名_new」となるように設定
const folderId = "画像ファイル保存用のフォルダID";
const folder = DriveApp.getFolderById(folderId);
const new_image = folder.createFile(image.blob.setName(datalist[0][4] + "_new"))
//指定したGoogleドキュメントのテーブル(表)に画像を挿入
const document = DocumentApp.openById(docCopyID);
const body = document.getBody();
const table = body.getTables()[0];
image = new_image.getBlob();
table.getCell(0, 5).appendImage(image);
//メール送信
mail(datalist, docCopyID);
}
画像挿入が最も苦戦しました。要注意点は、ドキュメントのテーブル(表)に画像を挿入する点です。
画像の取り込み自体は先人たちのBlobオブジェクト解説を見てなんとか攻略しました。
がしかし、テーブル(表)の端っこのセルにうまく挿入ができず大苦戦。ちなみに最初はgetCellメソッドで以下のように挿入範囲の指定をしていました。
//(参考)getCell()の使い方
getCell('指定した行', '指定した列')
//テーブルの(0行目、2列目)だと思い範囲指定して画像を挿入
table.getCell(0, 2).appendImage(image);
原因がつかめなかったので結合前の列数を確かめるべく、一旦「セルの結合を解除」してみました。すると、以下のように。
ということで画像挿入の指定範囲をgetCell(0,5)にして再挑戦したら、無事希望するセル(赤枠部分)にうまく表示できました。
③メールの自動送信
フォームの回答をトリガーにして、メールが送信されるプログラムです。
function mail(datalist, docCopyID) {
const file = DriveApp.getFileById(docCopyID);
const url = file.getUrl();//ファイルIDを入力
const mail = datalist[0][1]; //メールアドレスを指定
const mtitle = '【株式会社◯◯】履歴書作成完了!';//メールタイトル
const body = datalist[0][4] + '様\n\nこの度はお申込いただきありがとうございます。\n履歴書を頂戴しましたので、ご確認ください。\nなお、下記ドキュメントで編集が可能です。\n\n【履歴書】\n' + url;//メール本文
GmailApp.sendEmail(mail, mtitle, body);//メール送信
}
メール自動送信時の送信元アカウントに関しては先程も書きましたが、基本オーナーのアカウントになります。
今回はメールの文中に、自動生成したドキュメント(履歴書)のURLも載せて提出後の編集もできるようにとの依頼でしたので、①履歴書生成の関数内でaddEditorメソッドを使用し、編集権限を付与しています。
//(参考)addEditor()の使い方
addEditor("編集権限を付与するアカウントを入力")
//生成したコピードキュメントの編集権限を、指定アカウント(今回はフォーム回答者)に付与
DriveApp.getFileById(docCopyID).addEditor(datalist[0][1]);
①〜③のプログラム完成版
まとめるとこうなります。
function makeDoc() {
// テンプレートとするドキュメントIDを設定
let docId = "テンプレートとするgoogleドキュメントのID"
//生成したGoogleドキュメントを保存するフォルダを取得
let folder = DriveApp.getFolderById("保存するフォルダID");
// スプシを取得
let ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName("シート名を入力");
// スプレッドシートの最終行・最終列を設定
let lastRow = sheet.getLastRow();
let lastColumn = sheet.getLastColumn();
// ヘッダー行(=1行目)の値を取得
let header = sheet.getRange(1, 1, lastRow, lastColumn).getDisplayValues();
//最終行(=最新の回答)を取得
let datalist = sheet.getRange(lastRow, 1, lastRow, lastColumn).getValues();
//「yyyy/mm/dd」の部分を「年月日」に変換したい場合のみ使用
datalist[0][0] = Utilities.formatDate(datalist[0][0], 'Asia/Tokyo', 'yyyy年MM月dd日'); //申込日
datalist[0][6] = Utilities.formatDate(datalist[0][6], 'Asia/Tokyo', 'yyyy年MM月dd日'); //生年月日
// テンプレートとするドキュメントをコピーしてファイル名、保存先のフォルダを設定する
// 今回はファイル名を「氏名_履歴書」となるように設定
let docCopyID = DriveApp.getFileById(docId).makeCopy(datalist[0][4] + '_履歴書', folder).getId();
//生成したコピードキュメントの編集権限を、指定アカウント(今回はフォーム回答者)に付与
DriveApp.getFileById(docCopyID).addEditor(datalist[0][1]);
//コピーしたドキュメントを取得
let doc = DocumentApp.openById(docCopyID);
let body = doc.getBody();
//スプシの{ヘッダー文字列} → フォーム回答の文字列に置換
for (let i = 0; i < header[0].length; i++) {
body.replaceText('{' + header[0][i] + '}', datalist[0][i]);
}
//②画像挿入の関数を呼び出す
appendImage(ss, sheet, lastRow, lastColumn, header, datalist, docCopyID);
//GAS操作時はsaveコマンドで変更内容を反映させる
doc.saveAndClose();
//③メールの自動送信の関数を呼び出す
mail(datalist, docCopyID)
}
function appendImage(ss, sheet, lastRow, lastColumn, header, datalist, docCopyID) {
//Google Driveに保存された画像ファイルのID部分のみを取得
// → https://drive.google.com/open?id=◯◯の「◯◯」部分
//splitメソッドを使い、"id="の部分で分割する
let fileID = datalist[0][2].split("id=");
//IDの確認用
Logger.log(fileID[1]);
// imgAppライブラリのdoResizeを使用し、幅を160pxに変更
let image = ImgApp.doResize(fileID[1], 160);
// 新しく作成した画像ファイルをフォルダに保存
// 今回はファイル名を「氏名_new」となるように設定
const folderId = "画像ファイル保存用のフォルダID";
const folder = DriveApp.getFolderById(folderId);
const new_image = folder.createFile(image.blob.setName(datalist[0][4] + "_new"))
//指定したGoogleドキュメントのテーブル(表)に画像を挿入
const document = DocumentApp.openById(docCopyID);
const body = document.getBody();
const table = body.getTables()[0];
image = new_image.getBlob();
table.getCell(0, 5).appendImage(image);
//メール送信
mail(datalist, docCopyID);
}
function mail(datalist, docCopyID) {
const file = DriveApp.getFileById(docCopyID);
const url = file.getUrl();//ファイルIDを入力
const mail = datalist[0][1]; //メールアドレスを指定
const mtitle = '【株式会社◯◯】履歴書作成完了!';//メールタイトル
const body = datalist[0][4] + '様\n\nこの度はお申込いただきありがとうございます。\n履歴書を頂戴しましたので、ご確認ください。\nなお、下記ドキュメントで編集が可能です。\n\n【履歴書】\n' + url;//メール本文
GmailApp.sendEmail(mail, mtitle, body);//メール送信
}
まとめ
今回の機会をいただいたことで新たに以下の技術習得ができました。
ドキュメントのコピー生成
ドキュメント内のテーブルに画像挿入
imgAppライブラリの活用
編集権限の付与
特にドキュメント内のテーブルに画像挿入やimgAppライブラリの活用は、かなり良い経験になりました。
というのもGoogle先生はスプシやスライドへの画像挿入方法はいくつも事例を載せてくれているのですが、ドキュメントへの画像挿入事例はごくわずかでした。(or 私の調べ方が悪い?)
Chat GPTやBardが回答したコードも試してみましたが私では使いこなせなかったので、ネット民に聞いたりもしてこの辺を突破しました。
先人の皆様、ありがとうございます。
今回の経験を元に、今後も空き時間を活用しながらちょっとした自動化・効率化のシステム開発に挑戦しようと思います。
ではではまた。
よろしければサポートお願いします。いただいたサポートはデジタルツール活用に関する記事作成の活動費用にします!