診察予約システム（LINE×GAS）にプッシュメッセージ機能を追加

##概要
耳鼻科の開業医をしています。自院の予約システムをGASを使ったLINE Botで作成しました。
[1時間で出来る　LINE×GASで順番取り予約システムの作成]
(https://qiita.com/doikatsuyuki/items/842b675415abd463e499)
[LINE×GASで作成した順番取り予約LINE Botを改良]
(https://qiita.com/doikatsuyuki/items/d551b169b129206fc394)

今まで使用していた業者さんの予約システムは解約してしまったので、自作の予約システムの使い勝手を良くしていくしかありません。

診療を継続できないような緊急事態（医師が救急搬送に付き添うとか、医師の体調が悪くなるとか、停電とか）では、予約した患者さんが来院されても診療を受けることが出来ません。このような時に診察予約済みの患者さんに「現在来院されても診療できないので連絡ください」のようなプッシュメッセージを送る機能を追加しました。

##今回実装した機能
1.緊急事態には予約した患者さんにプッシュメッセージを送る
2.スタッフはLINEで受付時間中でも予約券の発券を停止できる
3.待ち時間が短い時は発券できない（予約者が順番に遅れることが多いので）
4.予約券に来院時間の目安を提示（予約者が順番に遅れないようにするため）

##概念図
バックエンドとしてGoogle Spread Sheetを利用しApp Script(GAS）でLINE botと連携。作成法はこちら  [1時間で出来る　LINE×GASで順番取り予約システムの作成](https://qiita.com/doikatsuyuki/items/842b675415abd463e499)
![「LINE Bot+APIで表現してアウトプット」　概念図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453374/f298bd7a-c0f0-8a5c-fca0-d185a287bf24.png)

Spread SheetのA1セルが発券済み番号、B1セルが診察中番号、C1セルが「1」の時に発券停止、D1セルが一人当たりの待ち時間（分）、E列に予約券を発行した患者さんのLINE userIDが記載されるようにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453374/34851209-6ccb-9b0c-42d1-0f70d3dd69c2.png)


##機能を追加
**1.Google Spread SheetのE列の空いてるセルにユーザーIDを登録する関数recordLineUserId(userId)を作成**

```js
function recordLineUserId(userId) {
  var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  // E列の空いているセルの行番号を取得する。（E1,E2が既に埋まっていたらnext=3となる）
  var next = activeSheet.getRange("E:E").getValues().filter(String).length + 1;
  Logger.log(next);
  // E列の空いてるセルにユーザーIDを登録する
  activeSheet.getRange(next, 5).setValue(userId);
};

```

**2.取得したLINEuserIdにプッシュメッセージを送る関数sendPushMessages()を作成**

```js
function sendPushMessages() {
  // E列のLINE userIdを取得する
  var userIdList = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange("E:E").getValues().filter(String).flat();

  // 重複を削除する(1ユーザー1プッシュメッセージしか送らないようにする)
  userIdList = Array.from(new Set(userIdList));
  Logger.log(userIdList);

  // プッシュメッセージを送信する
  for (var userId of userIdList) {
    Logger.log(userId);

    UrlFetchApp.fetch("https://api.line.me/v2/bot/message/push", {
      headers: {
        "Content-Type": "application/json; charset=UTF-8",
        Authorization: "Bearer " + ACCESS_TOKEN,
      },
      method: "post",
      payload: JSON.stringify({
        to: userId,
        messages: [
          {
            type: "text",
            text: "このメッセージは医院で何らかの緊急事態が発生し、診療できない状況時にお送りするものです。本日未来院の方はご来院頂いても診察できない可能性がありますので医院にお問い合わせ下さい。",
          },
        ],
      }),
    });
  }
}
```
**3.C1セル値を取得する関数getNumberC1()を作成、getNumberD1()も同様に作成**

```js
function getNumberC1() {
  //1. 現在のスプレッドシートを取得
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  //2. 現在のシートを取得
  var sheet = spreadsheet.getActiveSheet();
  //3. 指定するセルの範囲（C1）を取得
  var range = sheet.getRange("C1");
  //4. 値を取得する
  var value = range.getValue();
  //ログに出力
  return value;
}

```


**4.C1セルを｢1｣にする関数stopC1()を作成**

```js
function stopC1() {
  //1. 現在のスプレッドシートを取得
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  //2. 現在のシートを取得
  var sheet = spreadsheet.getActiveSheet();
  //3. 指定するセルの範囲（C1）を取得
  var rangeC = sheet.getRange("C1");
  //4. C1セル値を1にする：セルに値をセットする場合はsetValueを使う
  rangeC.setValue(1);
}

```

**5.待ち時間（分）を取得する関数getTime()を作成**

```js
function getTime() {
  //1. 現在のスプレッドシートを取得
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  //2. 現在のシートを取得
  var sheet = spreadsheet.getActiveSheet();
  //3A. 指定するセルの範囲（A1）を取得
  var rangeA = sheet.getRange("A1");
  //3B. 指定するセルの範囲（B1）を取得
  var rangeB = sheet.getRange("B1");
  //4A. 値を取得する
  var valueA = rangeA.getValue();
  //4B. 値を取得する
  var valueB = rangeB.getValue();
  //待ち人数
  var number = valueA - valueB - 1;
  //待ち時間
  var time = number * getNumberD1();
  //ログに出力
  return time;
}

```

**6.ifで分岐させる**

```js
//患者さんが[リッチメニューの予約券発行]をタップ
if (userMessage === "発券") {
  //休診日（日曜または土曜午後）
  if (day === 0 || (day === 6 && 1400 <= now)) {      
      messages[0].text = "土曜午後・日曜・祝日は休診日です";    
    //予約時間内　
    } else if ((830 <= now && now < 1100) || (1400 <= now && now < 1630)) {
     //C1セルが「1」の時は発券停止
      if (getNumberC1() === 1) {        
        messages[0].text = "現在発券が停止されています。医院にお問い合わせください。";
      } else {
        //待ち時間が12分以下なら発券しない
        if (getTime() <= 12) {
          messages[0].text =
            "現在待ち時間が12分以下のため予約券は発券できません。直接ご来院ください。";
        } else {
          //userIdを取得
          recordLineUserId(event.source.userId);
          // フレックスメッセージ（予約券）
          messages = getReservedTicket();
        }
      }
    } else {
      messages[0].text =
        "現在発券時間外です。受付時間は午前8:30~11:00 午後2:00~4:30です。";
    }
  }

```

**7.スタッフがLINEであるメッセージを送ると、発券が停止され、予約患者にプッシュメッセージが送られる**

```js
  //スタッフが「stop（仮）」をLINEに送ると発券停止  
  if (userMessage === "stop") {
    //C1セルが１に変更
    stopC1();   
    messages[0].text = "発券（患者用）を停止しました。";
  //スタッフが「push（仮）」をLINEに送るとプッシュメッセージ
  } else if (userMessage === "push") {
    sendPushMessages();
    messages[0].text = "プッシュメッセージを予約患者に送りました。";
  }
```
プッシュメーセージを送信できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453374/8f6d4748-082b-fef4-a488-63d306295ee4.png)


**8.getTime()を使い来院時間を表示**

```js
function getReservedTicket() {
  return [
    {
      type: "flex",
      altText: "＊＊耳鼻咽喉科　予約券",
      contents: {
        type: "bubble",
        body: {
          type: "box",
          layout: "vertical",
          contents: [
            {
              type: "text",
              text: "＊＊耳鼻咽喉科　診察予約券",
              weight: "bold",
              color: "#1DB446",
              size: "sm",
              align: "center",
            },
            {
              type: "text",
              text: String(getNumber()),
              weight: "bold",
              size: "5xl",
              margin: "xxl",
              align: "center",
            },
            {
              type: "separator",
              margin: "xxl",
            },
            {
              type: "box",
              layout: "vertical",
              margin: "xxl",
              spacing: "sm",
              contents: [
                {
                  type: "box",
                  layout: "horizontal",
                  contents: [
                    {
                      type: "text",
                      text: "・こちらの画面を受付でご提示下さい",
                      size: "sm",
                      color: "#555555",
                      flex: 0,
                    },
                  ],
                },
                {
                  type: "box",
                  layout: "horizontal",
                  contents: [
                    {
                      type: "text",
                      text: "・遅れた場合予約券は無効になります",
                      size: "sm",
                      color: "#555555",
                      flex: 0,
                    },
                  ],
                },
                {
                  type: "box",
                  layout: "horizontal",
                  contents: [
                    {
                      type: "text",
                      text: "・こまめに【待ち状況】をご確認下さい",
                      size: "sm",
                      color: "#555555",
                      flex: 0,
                    },
                  ],
                },
              ],
            },
            {
              type: "separator",
              margin: "xxl",
            },
            {
              type: "box",
              layout: "horizontal",
              margin: "md",
              contents: [
                {
                  type: "text",
                  text: "来院時間",
                  size: "sm",
                  color: "#4169e1",
                  flex: 0,
                },
                {
                  type: "text",
                  text: "発券から" + String(getTime() - 3) + "分後まで",
                  color: "#4169e1",
                  size: "sm",
                  align: "end",
                },
              ],
            },
          ],
        },
        styles: {
          footer: {
            separator: true,
          },
        },
      },
    },
  ];
};

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/453374/6d941e99-38b4-48a8-3b50-d1ee57f36599.png)

##考察
今回で元々考えていた機能は実装できました。無料でここまで使えるものが自作できるとは自分でも驚いています。今後はスタッフや患者さんの意見を聞きながら改良を続けていきたいです。色々と教えてくださったTakahiro Mitsuokaさん（[マイページはこちら](https://qiita.com/tmisuoka0423)）ありがとうございました！