【GAS】Google Apps Script 活用事例 マニュアル作成を効率化し、業務ノウハウの言語化を推進しようぜ★
Photo by Isis França on Unsplash
G Suiteは、便利だけど、明確なルールがないと、情報が分散しやすい.....かも
G Suiteは、URL一つで、閲覧・編集が出来るため、どこに保存したかという概念が希薄になりがちです。(※勤め先では、G SuiteのBasicプランなので、チームドライブを使っていません。なので大抵は個人のマイドライブにあったりします。Standardにしようぜ?)
見つからないマニュアルを根絶したい。
そして記憶から消えたファイルが人知れずに存在する....。マニュアルはどこかに存在するはずなんだけど、見つからない。そんな事ありませんか?
これは、僕が障害者雇用で、初めて人事部に配属になり、前任者が作成したジョブカン勤怠マニュアルを見つけたのが、担当者になってから半年後だったという実体験でもあります。
今回は、マニュアル作成の効率化について、ご紹介したいと思います。業務を引き継いだり、誰かが欠けても業務を滞りなく進めていく上で、マニュアルは、大事だと思っています。←当たり前
しかし、一度や二度ならず、欲しい情報・知りたい情報が書いていないマニュアルって読んだ事ありますよね?その反面、最終学歴や職務履歴が書いていない履歴書って読んだ事ありますか?ないと思います。
そこで考えたのが、GASで、差込文書作成の考え方を応用し、テンプレートを複製し、同じ書式フォーマットを適用、作成したドキュメントを、同じフォルダに格納し、集約させ、分散させない。そして、Slackからマニュアルをいつでも検索出来る。
そういうスクリプトを書けば、チームで使いやすいマニュアルが作成され、業務ノウハウの言語化が進むのではないかと考えました。
マニュアルに必要な情報とは?
概要
作業の目的
作業発生タイミング
作業完了のイメージ
スプレッドシートの構成
手順 1.
手順 2.
手順 3.
手順 4.
手順 5.
【参考画像】
【参考画像】
【参考画像】
【参考画像】 トリガーの設定方法
統一されたフォーマットでマニュアルを作るスクリプト
F列のステータスが空白になっている状態で、スクリプトを実行すると....
スクリプトで指定したフォルダに、作成されたマニュアルが自動で格納されます。その際、スプレッドシートに記載されていたマニュアル名と、担当者の名前が、挿入されます。
作成日、日付などが入力されます。シートを見て、リンクをクリックする事も出来るし、後述する方法で、Slack上で、見つける事も出来ます!!
G Suiteの検索は、検索エンジンを作った会社だけにめちゃくちゃ優秀です。単語が部分一致しさえすれば、どんな階層にあってもファイルを見つけ出す事が可能です。そのため、スクリプト上で、マニュアルという単語を加えています。
こうすれば、最悪タイトルが、分からなくても、「マニュアル」と検索すれば、該当するマニュアルを自力で探し当てる事が出来ます。
ソースコードはこんな感じ
//マニュアルを作るスクリプト
function createManual() {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getSheetByName('マニュアル作成');
const values = spreadsheet.getDataRange().getDisplayValues();
console.log(values.length);
for(let i = 1; i < values.length; i++){
if(values[i][0] !=='' && values[i][5] == ''){
const manualTitle = values[i][1] + ' マニュアル';
const name = values[i][2];
console.log(`i = ${i} マニュアル名 : ${manualTitle}\n${name}`);
/*マニュアルのテンプレート*/
const sourceDocument = DriveApp.getFileById('********************');
console.log(sourceDocument.getName());
/*コピーしたファイルの保存先*/
const copyDir = DriveApp.getFolderById('*********************');
/*コピーしたドキュメントの処理内容ここから*/
const duplicateDocument = sourceDocument.makeCopy(manualTitle, copyDir);//makeCopy(コピー後のリネーム、コピーしたファイルを保存する場所の指定)
const duplicateDocumentId = duplicateDocument.getId();
console.log(duplicateDocumentId);
/*コピーしたドキュメントのURLを生成*/
const createUrl = 'https://docs.google.com/document/d/' + duplicateDocumentId + '/edit';
console.log(createUrl);
//今日の日付をyyyy/MM/ddで出力
const date = new Date();
const today = Utilities.formatDate(date, 'JST', 'yyyy/MM/dd');
console.log(today);
/*
複製後のテンプレートを編集していく処理。別関数の呼び出し
modifyDocument(複製したテンプレートのID, マニュアルのタイトル, 部署名など)
*/
modifyDocument(duplicateDocumentId, manualTitle, name, date)
/*配列はゼロから始まる。rangeは1から*/
const rangeNumber = i + 1;
sheet.getRange(rangeNumber, 4, 1, 3)//複製したドキュメントのURLと、作成済みの2つを配列で書き込む
.clearContent()
.setValues([[today, createUrl, '作成済']]);
console.log(`シートに書き込まれる範囲: ${sheet.getRange(rangeNumber, 4, 1, 3).getA1Notation()}`);
}//if
}//for
}//end
function modifyDocument(duplicateDocumentId, manualTitle, name, today){
/*コピーしたドキュメントをIDで開く*/
const targetDocument = DocumentApp.openById(duplicateDocumentId);
console.log(targetDocument.getName());
const stringDate = Utilities.formatDate(today, 'JST', 'yyyy/MM/dd');
/*ドキュメントの内容を差し替え*/
const targetBody = targetDocument.getBody();
targetBody
.replaceText('マニュアルタイトル',manualTitle)
.replaceText('yyyy/MM/dd',stringDate)
.replaceText('部署・名前', name)
/*見出しの装飾*/
const h1 = targetBody.getParagraphs()[0].setHeading(DocumentApp.ParagraphHeading.HEADING1);//見出しにしても字は自然に太くならない
h1.editAsText()
.setFontFamily('Hiragino Kaku Gothic ProN')
.setFontSize(20)
.setForegroundColor('#00ab44');
/*部署・名前・作成者をグレーで小さめのフォント*/
const info = targetBody.getParagraphs()[1]
info.editAsText()
.setFontFamily('Hiragino Kaku Gothic ProN')
.setFontSize(10)
.setForegroundColor('#666666');
const info2 = targetBody.getParagraphs()[2]
info.editAsText()
.setFontFamily('Hiragino Kaku Gothic ProN')
.setFontSize(10)
.setForegroundColor('#666666');
/*小見出しを追加*/
const h2Array = ['概要','作業の目的','作業発生タイミング','作業完了のイメージ','スプレッドシートの構成','手順 1.','手順 2.','手順 3.','手順 4.','手順 5.','【参考画像】','【参考画像】','【参考画像】','【参考画像】 トリガーの設定方法'];
for (let j = 0; j < h2Array.length; j++){
const paragraph = targetBody.appendParagraph(h2Array[j]);
paragraph.setHeading(DocumentApp.ParagraphHeading.HEADING2) + '\n\n';
paragraph.editAsText().setForegroundColor('#00ab44');
}//for_j
}//end
エラーは出ないものの、.setFontFamily('Hiragino Kaku Gothic ProN') が上手く効いていないようです。
テンプレートファイルは、こんな感じ
「えっ......えらくシンプルじゃね?」と思った方が多いのではないでしょうか?ちゃんと理由があります。
Google documentのGASって、位置の特定とかが、結構大変です。位置の特定をする際、spreadsheetでいう、getActiveCell()の取得のように、getCursor()でマウスポインタの位置を取得して特定の文章を挿入したりするというのが一番簡単でした。
パラグラフと文章内容を把握するためのスクリプト
function myFunction() {
const document = DocumentApp.getActiveDocument();
const body = document.getBody();
const paragraphs = body.getParagraphs();
for(let i = 0; i < paragraphs.length; i++){
console.log(`paragraphs[${i}]: ${paragraphs[i].getText()}`);
}//for
}
上記スクリプトを、テンプレートのスクリプトエディタに貼り付けて実行してみてください。パラグラフと、文章内容の関係性が見えてきます。
appendParagraphメソッドは、文章の一番最後に追加します。追加する位置を指定できません。そういうわけで、テンプレートを至極シンプルにし、文章を後ろにガンガン加えていくという処理をしています。ちなみに位置をする必要がある場合は、insertText(index, text)を使います。
paragraphについては、文章を意味するHTMLの<p></p>タグをイメージしてもらうと分かりやすいかもしれません。一行の文章って感じです。
公式レファレンスに記載されていたサンプルだと、body.editAsText()、文章全体を一つのpタグで囲ったような処理になります。もしくは、段落を指定して、paragraphs[5].insertText('内容') みたいに書く事も出来ます。(出来たはず.....。)
段落の指定がねぇ...indexOfとかで見つける感じかなぁ...。
//文頭に追加する
const text = body.editAsText();
text.insertText(0, 'Inserted text.\n');
//文の最後に追加する
text.appendText('\nAppended text.');
見出しの追加が便利
paragraph.setHeading(DocumentApp.ParagraphHeading.HEADING2)
見出しの追加機能が結構便利です。目次を作るような機能です。この見出しがある事により、読み手が必要な箇所から読み進める事が出来ます。
作成されたマニュアルのURLを、SlackからSlash commandsで呼び出す
function doPost(e) {
const verificationToken = e.parameter.token;
if (verificationToken !== '*****************') { // AppのVerification Tokenを入れる
throw new Error('Invalid token');
}
const keyWords = e.parameter.text;
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getSheetByName('マニュアル作成');
const values = sheet.getDataRange().getValues();
let results = '';
const url = spreadsheet.getUrl();
const sheetId = sheet.getSheetId();
const marge = url + "#gid=" + sheetId;
const fileName = spreadsheet.getName();
// console.log(spreadsheet.getName());
// console.log(marge);
/*見出しを除いた状態でスタート*/
for(let i = 1; i < values.length; i++){
if(values[i][0] === ''){continue}
if(values[i][1].indexOf(keyWords) !== -1){
results += values[i][1] + '\n' + values[i][4] + '\n\n';
}
}//for_i
results += fileName + '\n' + marge;
console.log('マニュアル %s', results)
const response = { text: results }
return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
Slash Commandsの作り方
Slack APIにアクセスします。Slack APIに関しては、かなり頻繁にUIが変わります。スクリーンショットは、2020年6月現在の情報です。
以前までは存在した、Bot Userってメニューがなくなっていたりします。
次に、App Homeに、自分の名前など適当に入力します。これは以前のBot Userに該当します。
スプレッドシートから、Slackに通知する場合は、このあとに、Incoming Webhooksの設定をします。今回はSlash Commandsなので、不要です。
verificationTokenも、Basic Informationにあります。これはリクエストを受けた際に、Slackからアクセスを受けたものかを判定するものです。(....多分)
そしてSlack APIで作成中のアプリを、アプリを使いたいチャンネルにインストールしましょう!!
ウェブアプリケーションとして導入...をクリックする。
Request URLに、Current web app URLを入力します。そして保存する。
Slackで、/manual キーワード で検索してみよう
下記が、データベース代わりとなるスプレッドシートです。マニュアルをテンプレートに沿って作成する方法も紹介しています。
キタァぁぁぁ!!
いかがでしたか?今の会社は、結構自由な会社で、チームのSlackチャンネルをかなり広範にカスタマイズさせてもらっています。(それをメンバーが使いこなせているかというと.....そうではない現実がありますが....。)
Slash Commandsを浸透させるために、1週間に1回とか、通知してもいいかなと思いました。
Slackチャンネルに参加した人が、既存のカスタムコマンドの存在を知るために
Slackの有料会員だと、ワークフロービルダーという機能が使えます。これはノーコードで実装できます。
ワークスペースの逆三角形みたいなマークをクリックします。ワークフロービルダーをクリックします。
新しいメンバーが、特定のチャンネルに入ったら、こんな機能がありますよー。見てねー(調べもせず、聞くなよー)という事が出来ます。今回は、ワークフロービルダーの紹介がメインではないので、詳細は省きます。
読まない人対策として、画面録画のビデオなどのリンクを指定してあげると、より親切かもしれません。Macはcommand + shift + 5 Windowsは、windows + G で出来ます。
他のSlack関連の記事について
この記事が気に入ったらサポートをしてみませんか?