GoogleAppsScript
GoogleSpreadSheet

GASで簡易承認ワークフロー

概要

  • 簡易ワークフローを導入する必要があって作成した → 結局使ってない
  • 大きな組織には全く向いていない
  • 全体的にうろ覚え

だいたいのフロー

  1. フォームから申請
  2. 承認者にメールが届く
  3. 承認画面で承認もしくは却下
  4. 承認して、上位承認者がいる場合 → 2へ
  5. 却下した場合、申請者にメール
  6. 結果がspreadsheetへ書き込まれる

作り方

申請フォーム

  • 申請用フォームをGoogleFormで作成
  • ユーザーを手打ちで作成(大きな組織には全く向いていないと言った) 休暇申請.png

シート作成

回答→「回答先を選択」→緑色のマークをクリック→spreadsheetが作成される
screenshot-docs.google.com-2018-04-04-15-30-09.png

ユーザーのマスタ

  • ユーザー用のシートを追加
  • 申請者、承認者の関係を定義(authorizer_idに承認者のidを記入)

ユーザー一覧.png

階層のイメージ
階層.png

スクリプト作成

spreadsheetで「ツール」→「スクリプトエディタ」
スクリプトエディタへ.png

申請時メール送信

var URL = "https://script.google.com/macros/s/*********************************************/dev";
var sheetName = "フォームの回答";

function sendFormMail(e){

  // 追加行
  var row = e.range.getRow(); 
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName(sheetName);

  var name = e.namedValues["氏名"];
  var user = getUserBy("name", name);

  // 承認者
  var authorizer= getUserBy("id", user['authorizer_id']);
  var address = authorizer['mail'];

  var cols = ["タイムスタンプ","氏名","申請日","休暇取得申請書(自","休暇取得申請書(至","休暇種類","事前連絡","理由"];
  var body="";
  cols.forEach(function(col){
    body += col;
    body += ":"
    body += e.namedValues[col];
    body += "\n";
  });


  body += "url:"
  body += URL;
  body += "?row=" + row;
  body += "&name=" + encodeURI(authorizer['name']);

  MailApp.sendEmail(address,"休暇申請",body);
}

ユーザー情報取得

function getUserBy(key, value){
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sh = spreadsheet.getSheetByName('ユーザー');
  var values = sh.getDataRange().getValues();
  var keys = headerKeys(sh);

  for (var i = 0; i < values.length; i++) {
    var row = values[i];
    row = rowToHash(row, keys); 
    if (row[key] == value) {
      return row;
    }
  }
}
// ヘッダ行を取得
function headerKeys(sh) {
  return sh.getRange(1,1,1, sh.getLastColumn()).getValues()[0];
}
//行の情報をオブジェクトに変換
function rowToHash(array, keys) {
  var hash = {};
  array.forEach(function(value, i) {
    hash[keys[i]] = value;
  })
  return hash;
}

「URL」の部分

公開 -> ウェブアプリケーションとして導入
ウエブアプリケーションとして導入.png

URLのあれ.png

承認用フォーム

shonin.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <style>
    table, td, th {
      border-collapse: collapse;
      padding: 0px;

      border: 1px black solid;
    }
    td {
          margin: 5px;
    }
    </style>
  </head>
  <body>
    <h1>休暇申請承認</h1>
    <? var html = ''; ?>
    <? var json = getRowData(row); ?>
    <table>
    <? for(key in json){    ?>
    <? if (!json[key]) break; ?>
    <tr>
        <td><?= key ?></td>
        <? if (['申請日','休暇取得申請(自','休暇取得申請(至'].indexOf(key) > -1) { ?>
                <td><?= Utilities.formatDate( json[key], 'Asia/Tokyo', 'yyyy年M月d日'); ?></td>
        <? } else { ?>
                <td><?= json[key] ?></td>
        <? } ?>
    </tr>
    <? } ?>
    </table>
    <form action="<?= url ?>" method="post">
      <input type="radio" name="shonin" value="1" checked="checked">承認
      <input type="radio" name="shonin" value="0">却下
      <input type="hidden" name="row" value="<?= row ?>">
      <input type="hidden" name="name" value="<?= name ?>">
      <input type="submit" value="送信">
    </form>
  </body>
</html>

 承認ページ表示時スクリプト

// 承認ページ表示時
function doGet(e) {
  //必要な値を画面に持たせておく
  var row = e.parameter.row;
  var name = e.parameter.name;
  var html = HtmlService.createTemplateFromFile("shonin");
  html.row = row;
  html.name = name;
  html.url = URL;
  html.method = "get";
  return html.evaluate();
}

休暇申請承認.png

承認ページから送信時

function doPost(e) {

  var shonin = e.parameter.shonin;
  var row = e.parameter.row;
  var name = e.parameter.name;

  // シートに承認を記入
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName(sheetName);
  var values = sheet.getDataRange().getValues();

  var rowData = values[row-1];
  var idx = rowData.length;

  var status = "";
  var timestamp = Utilities.formatDate( new Date(), 'Asia/Tokyo', 'yyyy/MM/dd hh:mm:ss');

  var cur = -1;
  rowData.some(function(col,i){
    if (!col){//空の列を取得
      idx = i;
      return true;
    }
  });
  idx++;

  if (shonin == 1) {
    status = "承認";
  } else {
    status = "却下";
  }

  sheet.getRange(1, idx).setValue("状態");
  sheet.getRange(row, idx).setValue(status);
  idx++;

  sheet.getRange(1, idx).setValue("承認者");
  sheet.getRange(row, idx).setValue(name);
  idx++;

  sheet.getRange(1, idx).setValue("処理日次");
  sheet.getRange(row, idx).setValue(timestamp);

  // 承認者をメールに記載するため再取得
  values = sheet.getDataRange().getValues();

  if (shonin == 1) {

    // 次の承認者をさがす
    var user = getUserBy("name", name);
    Logger.log(user);

    var authorizer_id = user['authorizer_id'];

    if (authorizer_id) {//上位承認者がいる場合
      // 次の承認者にメール送信
      var authorizer = getUserBy("id", authorizer_id);
        sendMail(values, row, authorizer, true);
    }
  } else {
    // 申請者にメール送信
    var name = sheet.getRange(row, 2).getValue();
    var user = getUserBy("name", name);
    sendMail(values, row, user, false);
  }
  var html = HtmlService.createTemplateFromFile("complete");
  return html.evaluate();
}

function sendMail(values, row, user, approved){
  var rowData = values[row-1];
  var body="";
  rowData.forEach(function(col,idx){
    if (!col) return true;
    var key = values[0][idx];
    body += key;
    body += ":";
    if (['申請日','休暇取得申請書(自','休暇取得申請書(至'].indexOf(key) > -1) {
      body += Utilities.formatDate( col, 'Asia/Tokyo', 'yyyy年M月d日');
    } else if (['処理日時'].indexOf(key) > -1) {
      body += Utilities.formatDate( col, 'Asia/Tokyo', 'yyyy年MM月dd日 hh:mm:ss');
    } else {
      body += col;
    }
    body += "\n";
  });

  body += "url:"
  body += URL;
  body += "?row=" + row;
  body += "&name=" + encodeURI(user['name']);

  var address = user['mail'];
  var title = approved ? "休暇申請" : "休暇申請が却下されました";
  MailApp.sendEmail(address, title, body);
}

メール

メール.png

完了画面

complete.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    処理が完了しました
  </body>
</html>
完了.png

完了しました。よかったですね。