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

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

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

## 作り方
### 申請フォーム
- 申請用フォームをGoogleFormで作成
- ユーザーを手打ちで作成（大きな組織には全く向いていないと言った）
![休暇申請.png](https://qiita-image-store.s3.amazonaws.com/0/54295/e9c6aee5-a338-a6a7-7aca-a45f1a57640e.png)

### シート作成
回答→「回答先を選択」→緑色のマークをクリック→spreadsheetが作成される
![screenshot-docs.google.com-2018-04-04-15-30-09.png](https://qiita-image-store.s3.amazonaws.com/0/54295/cc77c0cb-47e3-1a10-25d1-0e3133b6958b.png)

### ユーザーのマスタ
- ユーザー用のシートを追加
- 申請者、承認者の関係を定義（authorizer_idに承認者のidを記入）

<img width="595" alt="ユーザー一覧.png" src="https://qiita-image-store.s3.amazonaws.com/0/54295/415d6611-bafc-8d70-fb03-1aed0df18c7c.png">

階層のイメージ
<img width="175" alt="階層.png" src="https://qiita-image-store.s3.amazonaws.com/0/54295/b7305e8a-e969-4ef6-4aea-2ccdfba68d1d.png">

## スクリプト作成
spreadsheetで「ツール」→「スクリプトエディタ」
![スクリプトエディタへ.png](https://qiita-image-store.s3.amazonaws.com/0/54295/b6e4d455-aece-0ead-ec3a-bca502ec5b12.png)

### 申請時メール送信

```js

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);
}
```

### ユーザー情報取得
```js
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;
}

////////////////////
// 追記しました
function getRowData(row){

  Logger.log(row);
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName(sheetName);
  Logger.log(sheet);
  var values = sheet.getDataRange().getValues();  
  Logger.log(values);
  var rowData = values[row-1];
  
  var body={};
  rowData.forEach(function(col,idx){
    body[values[0][idx]] = col;
  });
  
  return body;
}
```
### 「URL」の部分
公開 -> ウェブアプリケーションとして導入
<img width="564" alt="ウエブアプリケーションとして導入.png" src="https://qiita-image-store.s3.amazonaws.com/0/54295/14801954-6eea-3ce0-edb8-2e85a1d78865.png">

<img width="456" alt="URLのあれ.png" src="https://qiita-image-store.s3.amazonaws.com/0/54295/e5a21225-5ee2-561d-3489-de3f5e60a3cf.png">

追記ここから>>>
### トリガーの設定
<img width="832" alt="休暇申請のコピー_と_kwgch_—_develop_tk2-247-33289__virtual_develop_public_html_webnews_crawler_—_ssh_develop_160_16_211_43_-p_10022_—_144×42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54295/fcfe6079-d69e-438b-2cb2-54a77a786ee2.png">

<img width="1662" alt="Apps_Script_-_プロジェクト_トリガー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54295/d5807587-f147-b972-869a-81f3d3db3621.png">

「イベントの種類を選択」は「フォーム送信時」を選択して保存
<img width="786" alt="Apps_Script_-_プロジェクト_トリガー2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54295/e5eaefb1-1d18-2cf8-1fc9-437c69c4201d.png">

<img width="606" alt="ログイン_-_Google_アカウント.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54295/415395b9-8ec3-1952-aa8e-82350bdaf644.png">

<img width="587" alt="ログイン_-_Google_アカウント2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54295/69314510-69fb-8d7b-dfa8-249bdd73908c.png">

<img width="591" alt="ログイン_-_Google_アカウント_と_Apps_Script_-_プロジェクト_トリガー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54295/0959b204-7e21-395c-dfbe-00ac906326f8.png">

トリガーが追加されました
<img width="1646" alt="Apps_Script_-_プロジェクト_トリガー3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54295/8258cc59-6500-4824-bba1-66e539d5e94a.png">

\>>>追記ここまで

### 承認用フォーム
```html: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="０">却下
      <input type="hidden" name="row" value="<?= row ?>">
      <input type="hidden" name="name" value="<?= name ?>">
      <input type="submit" value="送信">
    </form>
  </body>
</html>
```

###　承認ページ表示時スクリプト
```js
// 承認ページ表示時
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();
}
```
<img width="575" alt="休暇申請承認.png" src="https://qiita-image-store.s3.amazonaws.com/0/54295/2c44ef87-afd2-f6ca-f571-94c6d8e06cd5.png">

### 承認ページから送信時
```js
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);
}
```

### メール
<img width="425" alt="メール.png" src="https://qiita-image-store.s3.amazonaws.com/0/54295/17575a49-19b4-91bc-09fe-b21f6f948796.png">


### 完了画面
```html:complete.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
  </head>
  <body>
    処理が完了しました
  </body>
</html>
```

| <img width="424" alt="完了.png" src="https://qiita-image-store.s3.amazonaws.com/0/54295/3c57fdb4-2ddc-397c-c6aa-fcc74bcc83ee.png"> |
---

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

---
追記

### コードを修正した場合
<img width="791" alt="「GASで簡易承認ワークフロー_」を編集_-_Qiita.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54295/9660e137-bc6c-34c0-2eb6-135c97aee082.png">
メニューの"公開"から"ウェブアプリケーションとして導入..."をクリック

<img width="572" alt="休暇申請のコピー2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/54295/5e67f8e7-924e-b37b-a897-c1fe6ffb3184.png">

プロジェクトバージョンをNewにして公開しないと、変更が反映されないみたいです。

参考：https://qiita.com/YutakaArai/items/dcb14c94de1dde330fbd