見出し画像

第13話 予約の確認をする

こんにちは!Kenです!

前回までで、一連の予約操作が実装できました。特に第12話は「非同期処理」も入り重要な回でした。

今回は、以前予約した内容確認のリプライを返すことを目標としたいと思います。

イメージはこんな感じです。

画像1

予約確認イベント

予約確認処理の起点は、お客様側から確認したいというメッセージを送ることです。何でも良いのですが、今回は「予約確認」というメッセージが送られたら次回予約内容をリプライする処理を実装しましょう。

「予約確認」というメッセージのイベント種別はもうお分かりですね。イベントタイプは"message"となりますので、handleMessageEvent関数内の処理を追加していきます。

const handleMessageEvent = async (ev) => {
   console.log('ev:',ev);
   const profile = await client.getProfile(ev.source.userId);
   const text = (ev.message.type === 'text') ? ev.message.text : '';
   if(text === '予約する'){
     orderChoice(ev);
   }else if(text === '予約確認'){
     checkNextReservation(ev)
   }else{
       return client.replyMessage(ev.replyToken,{
           "type":"text",
           "text":`${profile.displayName}さん、今${text}って言いました?`
       });
   }
}

else if (text === '予約確認')を追加しました。とりあえず、checkNextReservation( )関数を実行するようにしてます。では、その関数を実装していきましょう。

checkNextReservation関数の実装

checkNextReservation関数の内容をざっくり書くと次の通りです。

■まず予約データベースのデータを全て取得する
■取得したデータから、「予約確認」メッセージを送った人のLINE IDに一致するものを抽出する
■抽出した予約の中から過去の予約は排除し、未来の予約のみをピックアップする

データベースには同じ人であっても、過去・未来含めたデータが蓄積しています。そのため、現時点よりも未来の予約をピックアップする必要があります。

また、データベースとのやり取りの処理なので、少し時間がかかるということは前回学習しましたね。勘の良い方は「非同期処理」を使うんだなと想像できたかもしれません。

では、checkNextReservation関数の中身です。

const checkNextReservation = (ev) => {
 return new Promise((resolve,reject)=>{
     // ここに処理を書いていく
 });
}

まずは、これですね。もうバカの一つ覚えのように書けるようになりましょう。では中身を書いてみましょう。

const checkNextReservation = (ev) => {
 return new Promise((resolve,reject)=>{
   const id = ev.source.userId;
   const nowTime = new Date().getTime();

   const selectQuery = {
     text:'SELECT * FROM reservations;'
   };
   connection.query(selectQuery)
     .then(res=>{
       if(res.rows.length){
         const nextReservation = res.rows.filter(object1=>{
           return object1.line_uid === id;
         })
         .filter(object2=>{
           return parseInt(object2.starttime) >= nowTime;
         });
         console.log('nextReservation:',nextReservation);
         resolve(nextReservation);
       }else{
         resolve([]);
       }
     })
     .catch(e=>console.log(e));
 });
}

nowTimeは現在時刻のタイムスタンプを取得してます。これは現在よりも未来の予約データを取得するための比較基準とするためです。

そして、SELECTクエリ文ではreservationsテーブルに入っているデータを全て取得します。

このクエリ文を実行すると、予約データはres.rowsに格納されます。このres.rowsにfilterメソッドにより、まず1段目で「予約確認」メッセージを送った人のLINE IDに一致する予約をフィルタリングし、次に連続してnowTimeよりstarttimeが大きなものだけを抜き出してます。つまり現時点より未来の予約です。そしてresolveによってその予約データを返してあげてます。filterは便利なので、ぜひ使い方をマスターしましょう。

またelse{ resolve([ ]) }ですが、最初にreservationsテーブルに何も入っていなかった場合に空配列を返してあげるものです。これは返してあげた先での処理が、.lengthによって返る配列の長さによって条件分岐しているためです。

handleMessageEvent関数への追加コード

さて、handleMessageEvent関数をしっかり書いてみましょう。

else if(text === '予約確認'){
     const nextReservation = await checkNextReservation(ev);
     const startTimestamp = nextReservation[0].starttime;
     const date = dateConversion(startTimestamp);
     const menu = MENU[parseInt(nextReservation[0].menu)];
     return client.replyMessage(ev.replyToken,{
       "type":"text",
       "text":`次回予約は${date}${menu}でお取りしてます\uDBC0\uDC22`
     });
   }

checkNextReservation関数はデータベースとのやりとりなので、戻り値はプロミスです。なので、awaitを使って戻り値が返るまで待機させます。

ちなみにnextReservationをconsole.logすればわかりますが、配列の中に一つのオブジェクトが入った形式ですので[0]をつけてあげてます。

最終的にはリプライメッセージで、「??月??日(?) ??:??に、"メニュー名"でお取りしてます」と返したいと思います。

メニュー名は簡単です。グローバル変数に次の配列を用意します。

const MENU = ['カット','シャンプー','カラーリング','ヘッドスパ','マッサージ&スパ','眉整え','顔そり'];

そして、nextReservation[0].menuはメニュー番号(ただし文字列)を表してますので、整数型へパースしてあげたものをMENU配列のindexとしてあげれば簡単にメニュー名を得ることができます。

予約日時はstarttimeのタイムスタンプを??月??日(?)??:??に変換してあげたいと思います。そのために導入したのが、dateConversion関数です。

dateConversion関数の実装

以前、日時をタイムスタンプへ変換する関数timeConversionを作りました。今回はその逆、タイムスタンプを任意の日時、時刻の文字列へ変換します。これも後々、多用するかもしれないので、外出し関数として作ることにします。

const dateConversion = (timestamp) => {
 const d = new Date(parseInt(timestamp));
 const month = d.getMonth()+1;
 const date = d.getDate();
 const day = d.getDay();
 const hour = ('0' + (d.getHours()+9)).slice(-2);
 const min = ('0' + d.getMinutes()).slice(-2);
 return `${month}${date}日(${WEEK[day]}) ${hour}:${min}`;
}

timestampはデータベースから取得したものですので、文字列型になってます。そのため、整数型へパースしてあげる必要があります。

getMonth()メソッドで得られる数値は本来の値から1少ない数ですので、+1してあげてます。

getHours()メソッドで得られる時間の数字は日本標準時間に変換するために+9としてあげてます。

hour,minは('0' + xxxx).slice(-2)と見慣れない構文となってます。これは、単なるgetHours()やgetMinutes()だと、11:0とか9:0になってしまって見栄えが悪いので、有効数字2桁で、もし1桁の場合先頭に0をつけるための記述です。

また曜日の表示を標準化するためにグローバル変数にWEEKを宣言しました。

const WEEK = [ "日", "月", "火", "水", "木", "金", "土" ];

d.getDay()で得られる値は数値です。0→'日曜日',1→月曜日,・・・といった具合なので、WEEK関数を作りd.getDay()で得られた値をそのindexに使うと簡単に曜日の文字列が得られます。

デプロイ&確認

ではherokuへデプロイし確認してみましょう。

画像2

うまくいきましたね!!

予約をいくつも入れ過ぎてしまった人は

前回はようやく予約をデータベースへ格納することができるようになりました。嬉しくてたくさん予約を入れまくった人もいるのではないでしょうか。

実は私もそうです(笑)

スクリーンショット 2020-10-05 21.20.36

こんな感じです。

このように予約をいくつも入れてしまった人は予約確認が正しく出来ないと思います。予約は何回でも出来てしまいました。未来に複数の予約が入っている場合は、正しくリプライが返ってこないです。そのため、予約は1人1つしか出来ないようにする必要があるのです。この辺もおいおい実装する必要があります。

とりあえず、この4つある予約を1つに削除しましょう。データを削除するクエリ文は以下です。

DATABASE=> delete from reservations where id = 4;

この場合、id=4のデータが消えることになります。これを繰り返し、5と6も削除すると予約データはid=1の1つだけが残ることになります。

この状態で、予約確認をするとしっかり日時とメニューが表示されたのではないでしょうか。

それでは、今回はこの辺で!!

少しでも参考になりましたら「スキ」をいただけると幸いです。

最後までお読みいただきありがとうございました。

MENTA でLINEBOT開発サポートをしております。お気軽にご相談ください。



この記事が気に入ったらサポートをしてみませんか?