Google App Script でフォーム送信後のメール承認フローを作る

# 概要
Google 共有ドライブの利用を申請するフォーム送信後のメール承認フローを作ります。

```mermaid

graph LR

申請者
フォーム["Google<br>フォーム"]
管理者
共有ドライブ[Google<br>共有ドライブ]

申請者 --> |必要事項を記入し提出|フォーム

%%フォーム --> |承認依頼通知|管理者
管理者 ~~~ フォーム --> |承認依頼通知|管理者
フォーム ~~~ 管理者

%%管理者 --> |承認|申請者
申請者 ~~~ 管理者 --> |承認|申請者
管理者 ~~~ 申請者

%%管理者 --> |権限付与|共有ドライブ
共有ドライブ ~~~ 管理者 --> |権限付与|共有ドライブ


共有ドライブ --> |通知|申請者

申請者 ~~~ 共有ドライブ
```

# Google フォームを作成し、回答をスプレッドシートに保存
以下の記事を参考に「Google フォームを作成し、回答をスプレッドシートに保存」するところまで実施します。

フォームの各項目は必要に応じて調整できます。

https://tonari-it.com/google-form-purchase-of-equipment/

# 承認メールを飛ばす
以下の記事を参考に「メール承認フローを作る」ことができます。

https://qiita.com/nanana_monokaki/items/0b78f1ef755d70d041d1

https://solutions.system-exe.co.jp/appremo/blog/self-workflow

トリガーをフォーム送信時として、承認を依頼するメールを飛ばす Google App Script です。

```javascript:code.gs
FormApp.getActiveForm()
function onFormSubmit(e) {
    // プレーンテキストのメール本文
    let body = "承認をお願いいたします。\n\n"
    // HTMLテキストのメール本文
    let htmlBody = "<p>承認をお願いいたします。</p>"

    // GoogleFormから回答内容を取得します
    let itemResponses = e.response.getItemResponses();
    let approverMail =  "approver@exmaple.jp";
    let title = "Google 共有ドライブ利用申請"

    for (var i=0; i < itemResponses.length; i++) {
        let formData = itemResponses[i];
        let formLabel = formData.getItem().getTitle();
        let response = formData.getResponse();

        switch (formLabel) {
        case "1. 所属部署":
            dept = response;
            break;
        case "2. 申請者名":
            name = response;
            break;
        case "3. メールアドレス":
            recipientMail = response;
            break;
        }
    }

    let spreadSheetId = "xxxxxxxxxxx"; // スプレッドシートのID
  let spreadsheet = SpreadsheetApp.openById(spreadSheetId);
  let activeSheet = spreadsheet.getActiveSheet();
  let lastRowNo = activeSheet.getLastRow(); 

  // 次で作るウェブアプリの URL
  let url = "https://script.google.com/xxx/exec?row=" + lastRowNo;

  body = body + "件名：" + title + "\n所属部署：" + dept + "\n申請者名："+ name + "\nメールアドレス：" + recipientMail;
    htmlBody = htmlBody + "<span>件名：" + title + "</span><br><span>所属部署：" + dept + "</span><br><span>申請者名：" + name + "</span><br><span>メールアドレス：" + recipientMail + "</span><br><br>";
    htmlBody = htmlBody + "<a href=" + url + "&status=approve" + ">承認</a>　<a href=" + url + "&status=deny" + ">否認</a>";
    GmailApp.sendEmail(approverMail, title, body, {htmlBody: htmlBody});
}
```

# 承認ウェブアプリを作成
`doGet()` という関数を利用して、`approval.gs` をウェブアプリとして公開します。

ウェブアプリを作成後、先ほど作成した `code.gs` の `url` を更新します。

```javascript:approval.gs
function doGet(e) {
    let spreadSheetId = "xxxxxxxxxxx";
    let spreadsheet = SpreadsheetApp.openById(spreadSheetId);
    let activeSheet = spreadsheet.getActiveSheet();
    // スプレッドシート4列目のデータ、最終行に該当するセルの値をメールアドレスとして取得
    let targetEmail = activeSheet.getRange(e.parameters.row, 4).getValue();

    let req_body = {
      // コンテンツ管理者として権限を付与
      "role" : "fileOrganizer",  // organizer, fileOrganizer, writer, reader
      "type" : "user",    // user, group, domain, anyone
      "emailAddress": targetEmail // Email address
    };
    const query_param = {
      // 権限付与時の通知を送信する
      "sendNotificationEmail" : true,
      "supportsAllDrives" : true
    };
    let html = "";

    if (e.parameters.status == "approve") {
        html = "<h1>承認しました。</h1>";
        // Google Drive API V3
        Drive.Permissions.create(
          req_body, "ここに共有ドライブの ID を入れる", query_param
        );
        console.log("Permission added : Google 共有ドライブにコンテンツ管理者として権限を付与しました")
        activeSheet.getRange(e.parameters.row, 6).setValue('承認');
    } else {
        html = "<h1>否認しました。</h1>";
        activeSheet.getRange(e.parameters.row, 6).setValue('否認');
    }

    return HtmlService.createHtmlOutput(html);
}
```

# 参考 : Google 共有ドライブへの権限付与
以下の記事を参考にしました。

https://qiita.com/o-_-o/items/4055492d05abecf6b637

# 参考 : Google Drive API V3
Google Drive API V3 では、少しパラメータやメソッドが変更になっているので、V2 のコードから修正する必要がありました。

https://qiita.com/Cheap-Engineer/items/26a4ee6a0db29cc9a558

https://developers.google.com/workspace/drive/api/reference/rest/v3/permissions/create