GASで作る社内ツール (出張申請編)

# TL;DR
* 社内の出張申請・報告フローをGASを使って簡易化
* 今回は「申請」について記載
* 今後は「キャンセル」と「報告」を実装予定 

# はじめに
Qiita投稿2回目の箱猫です。今回は弊社が誇るハイパー煩雑業務フローの一つ、出張申請・報告をGASを使って簡易化しようとしている件について投稿します。

# 出張申請・報告の既存フロー
弊社では、外部打ち合わせや研修・講演会などで外出する場合は、部長と所属部署の事務員にGoogleカレンダーで予定を共有し、「欠席=承認」という謎の方程式でもって申請を行います。このとき予定のタイトルには、その月で何番目の出張かを示す通し番号を振ります。各自で回数を管理する必要があり、これまた余計なところで脳のリソースが割かれていきます。申請者レイヤーにおける作業はここまでですが、連絡を受ける事務員はこれだけでは済みません。下図に全体のフローを示します。

![既存フロー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131278/616ff19b-0c1f-9870-e459-543fda85b01a.png)

左下、2. ISO監査シートに出張情報を入力、とありますが、事務員はカレンダーの予定が共有されるたびにこの作業を行います。去年度は1200件ほどと仰っていたので相当の時間をEXCEL操作に費やしているわけです (ちなみにEXCELの監査シートは事務員PCローカルにあります) 。 平安時代の式神システムのほうが遥かに自動化できていることが伺いしれます。

出張に行った後はEXCELで報告書を書き、部長と事務員にメールで送ります。このとき、カレンダーの予定に含めた通し番号を忘れてはいけません。何番だったかなんてもう覚えていませんね？

# GASによる出張申請・報告システム
## フロー
![新規フロー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131278/35ba123b-07b5-874c-a1ae-8a95d6135628.png)

GASで作成するWebアプリケーションの機能は出張の新規申請、キャンセル、報告書・資料の送信の3つになります。今回は「申請」のみ紹介しますが、今後は申請した予定のキャンセル機能と報告書送信機能を実装していく予定です。

## 実装
GASはスプレッドシートなどに紐付けたものとスタンドアロンなものとで実装できますが、今回はスプレッドシートをデータベース代わりに使用したかったため前者で実装していきます。
なお本アプリケーションを通して初めてHTMLとHTMLに絡むJavascriptを書いておりますので、お作法などご指摘いただけると嬉しいです。

### コード.gs
Googleサーバで動くGASの処理を記述するファイル (という認識でいる) 。Webアプリケーションとして公開時に発行されるURLにアクセスした際にコールされる `doGet` 関数を必ず定義する必要がある。ここでは後述する hello_webapp.html を指定して `HtmlService` オブジェクトを返している (=ブラウザに表示される) 。

```javascript
function doGet() {
  // Webアプリケーションアクセス時に表示するHTMLファイルの指定 (hello_webapp.html)
  return HtmlService.createTemplateFromFile("hello_webapp").evaluate();
}
```

スプレッドシート内のシート名を会社アドレスのユーザ名で管理すればアクセス時に楽かと思い、接続しているアカウントのユーザ名を取得する `getExecuteUser` 関数を定義した。

```javascript
function getExecuteUser() {
  sessEmail = Session.getActiveUser().getEmail();
  sessId = sessEmail.split('@');
  return sessId[0];
}
```

後述する index.js.html 経由で、入力されたフォームデータをスプレッドシートにセットする関数。

```javascript
function setAppValues2Sheet(arg_when_st, arg_when_ed, arg_where, arg_why) {
  var sessionId = getExecuteUser(); 
  var spSheet = SpreadsheetApp.getActive();
  var ss;
  
  if (spSheet.getSheetByName(sessionId)==null) {
    // 申請者のシートが存在しない場合は新規作成
    ss = spSheet.insertSheet(sessionId);
  }
  else{
    // 既存シートから申請者シートを取得
    ss = spSheet.getSheetByName(sessionId);
  }
  
  ss.appendRow([arg_when_st, arg_when_ed, arg_where, arg_why]);
}
```

### hello_webapp.html
最初はCSSなど知らなかったので野暮ったい見た目のフォームになってしまった。ここではBootstrapで精一杯のおしゃれを試みている。ジャンボトロンって語感が可愛い。
Webアプリケーションの3機能「申請」「キャンセル」「報告」をタブレイアウトで切り替えられるようにしている。

```html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <!--- BootstrapのCSS読み込み --->
    <?!= HtmlService.createHtmlOutputFromFile('css_bootstrap').getContent(); ?>
  </head>
  <body>
    
    <div class="jumbotron jumbotron-fluid">
      <h1 class="display-4"> 出張申請・報告フォーム </h1>
    </div>
    
    <div class="container-fluid">
      <!--- タブのレイアウトを記述 --->
      <ul class="nav nav-tabs nav-fill" id="myTab" role="tablist">
        <li class="nav-item">
          <a class="nav-link active" id="app-tab" data-toggle="tab" href="#application" role="tab" aria-controls="application" aria-selected="true">新規申請</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" id="calcel-tab" data-toggle="tab" href="#cancel" role="tab" aria-controls="cancel" aria-selected="false">キャンセル</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" id="report-tab" data-toggle="tab" href="#report" role="tab" aria-controls="report" aria-selected="false">報告書提出</a>
        </li>
      </ul>
         
      <!--- タブ内のコンテンツを記述 ---> 
      <div class="tab-content" id="myTabContent">
        <!--- 申請 --->
        <div class="tab-pane fade show active" id="application" role="tabpanel" aria-labelledby="app-tab">
          <br> <p> 誰が:  <?= getExecuteUser() ?> </p>
          <form>
            <div class="form-row">
              <div class="form-group col-md-6">
                <label for="app_input_when_st">いつから</label>
                <input type="date" class="form-control" id="app_input_when_st">
              </div>
              <div class="form-group col-md-6">
                <label for="app_input_when_ed">いつまで (終日の場合は空欄) </label>
                <input type="date" class="form-control" id="app_input_when_ed">
              </div>
            </div>
            
            <div class="form-group">
              <label for="app_input_where">どこに</label>
              <input type="text" class="form-control" id="app_input_where" placeholder="本社、大学 など">
            </div>
            <div class="form-group">
              <label for="app_input_why">何のために</label>
              <input type="text" class="form-control" id="app_input_why" placeholder="試験、打ち合わせ など">
            </div>
            
            <button type="button" class="btn btn-outline-primary" onClick="application_setAppValues();">申請</button>
          </form>
        </div>
        
        <!--- キャンセル --->
        <div class="tab-pane fade" id="cancel" role="tabpanel" aria-labelledby="cancel-tab">
          <p> To Be Implemented </p>
        </div>
        
        <!--- 報告 --->
        <div class="tab-pane fade" id="report" role="tabpanel" aria-labelledby="report-tab">
          <p> To Be Implemented </p>
        </div>
      </div>
    </div>
    
    <!--- 外部JSの読み込み --->
    <?!= HtmlService.createHtmlOutputFromFile('index.js').getContent(); ?>
    <!--- BootstrapのJS読み込み --->
    <?!= HtmlService.createHtmlOutputFromFile('js_bootstrap').getContent(); ?>
  </body>
</html>
```

### index.js.html
GASではHTMLファイルしか受け付けないため、一般のWebアプリにおける index.js の中身を `<script>` タグで囲ったHTMLファイルとして作成する必要がある。役割としては、WebアプリにおけるフォームデータをGAS側に流すことだと解釈している (MVVMにおけるVM？) 。

```javascript
<script>

function application_setAppValues() {  
  // ページ上のフォームから情報を取得
  var info_when_st = document.getElementById("app_input_when_st").value;
  var info_when_ed = document.getElementById("app_input_when_ed").value;
  var info_where = document.getElementById("app_input_where").value;
  var info_why = document.getElementById("app_input_why").value;
  
  // gas側のコードにフォーム情報を渡す
  google.script.run.setAppValues2Sheet(info_when_st, info_when_ed, info_where, info_why);
}

</script>
```

### js_bootstrap.html・css_bootstrap.html
GAS製WebアプリケーションにおいてBootstrapを読み込むためのファイル。

```html
<!--- js_bootstrap.html --->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>

<!--- css_bootstrap.html --->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
```

## 出来上がったもの

![webform.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131278/e853f3da-9edb-f7ce-8b1d-79ff63922a0f.png)

ジャンボトロンくん、名前は可愛いけど背景色が可愛くないな・・・
申請ボタンを押すことで、紐付けられたスプレッドシートにフォーム情報が入力されるところまでは動作確認済み。
見た目の改善もぼちぼちしたいが、まずは全体を通して動くものの実装を進めていきたい。

# 今後の展望
* 申請した内容をカレンダーに予定作成する機能
* キャンセルタブにアクセス者のこれまでの申請情報一覧を表示し、選んだ申請内容を削除する機能の実装
* 報告書提出タブにファイルアップロード機能や報告内容を記載する入力フォームの実装

# 終わりに
初めてのWebアプリケーションということで覚束無い箇所も多々あります (nav-tabsがうまく表示されないなど) が、初めてということで楽しく実装していきます。書き方のお作法など御助言いただければ今後のためになりますのでとても嬉しいです。
最後まで読んでくださってありがとうございました。次回「キャンセル編」でまたお会いしましょう。

## 追記 (2019/12/02)
次回 [GASで作る社内ツール (キャンセル・報告編)](https://qiita.com/nyanko-box/items/905d224d3ba8d3d00796) を投稿しました。