「Linux」カテゴリーアーカイブ

【Laravel】【ホテル予約管理】クレジット情報を登録する(訂正)

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

さて、まずはユーザー登録時にクレジット情報を登録するようにしましょうか。

本来ならば、SSL通信によって、暗号化した状態でデータを送信しなくちゃいけないのですが、今はローカル環境でテスト用に動かしているので、そこら辺は考慮しないことにします。

考慮する場合は、NginxなどのWebサーバの設定が必要になります。

こちら、自前のクラウドサーバをHTTPSに対応した記事が参考になると思います。

それではコーディングしていきます。

やっていることは、以前にも書いた、usersテーブルにカラムを追加していく、ということです。

まずはマイグレーションの記述。

class AddCreditInfoUsers extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('credit_number')->after('phone');
            $table->string('mm')->after('credit_number');
            $table->string('yy')->after('mm');
            $table->string('credit_name')->after('yy');
            $table->string('code')->after('credit_name');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('credit_number');
            $table->dropColumn('mm');
            $table->dropColumn('yy');
            $table->dropColumn('credit_name');
            $table->dropColumn('code');
        });
    }
}

ビューの記述。

                        <div class="form-group{{ $errors->has('credit') ? ' has-error' : '' }}">
                            <label for="credit" class="col-md-4 control-label">credit number</label>

                            <div class="col-md-6">
                                <input id="credit" type="credit" class="form-control" name="credit" required>

                                @if ($errors->has('credit'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('credit') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group">
                            <label for="mmyy" class="col-md-4 control-label">MM / YY</label>

                            <div class="col-md-6">
                            <select name="mm">
                                <option value="1">01</option>
                                <option value="2">02</option>
                                <option value="3">03</option>
                                <option value="4">04</option>
                                <option value="5">05</option>
                                <option value="6">06</option>
                                <option value="7">07</option>
                                <option value="8">08</option>
                                <option value="9">09</option>
                                <option value="10">10</option>
                                <option value="11">11</option>
                                <option value="12">12</option>
                            </select>
                            /
                            {{Form::selectYear('yy', 2020, 2030)}}
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('credit_name') ? ' has-error' : '' }}">
                            <label for="credit_name" class="col-md-4 control-label">credit name</label>

                            <div class="col-md-6">
                                <input id="credit_name" type="credit_name" class="form-control" name="credit_name" required>

                                @if ($errors->has('credit_name'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('credit_name') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('code') ? ' has-error' : '' }}">
                            <label for="code" class="col-md-4 control-label">security code</label>

                            <div class="col-md-6">
                                <input id="code" type="code" class="form-control" name="code" required>

                                @if ($errors->has('code'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('code') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

入力項目に、クレジット番号、有効期限(MM/YY)、名義、セキュリティコードを追加しました。

バリデーション処理です。

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'fullname' => 'required|string|max:255',
            'address' => 'required|string|max:255',
            'phone' => 'required|digits:11',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
            'credit' => 'required|digits:16',
            'mm' => 'required|numeric',
            'yy' => 'required|numeric',
            'credit_name' => 'required|string|max:255',
            'code' => 'required|digits:3',
        ]);
    }

登録処理です。

    protected $fillable = [
        'name', 'fullname', 'address', 'phone', 'email', 'password', 'credit_number', 'mm', 'yy', 'credit_name', 'code'
    ];
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'fullname' => $data['fullname'],
            'address' => $data['address'],
            'phone' => $data['phone'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
            'credit_number' => encrypt($data['credit']),
            'mm' => $data['mm'],
            'yy' => $data['yy'],
            'credit_name' => encrypt($data['credit_name']),
            'code' => encrypt($data['code'])
        ]);
    }

クレジット番号、名義、セキュリティコードは機密性が高いため、encrypt()(※2020/2/5訂正)で暗号化してわからない形でデータベースに保存します。

※bcrypt()では、復号化できないようです。後ほどこの情報を使って自動決済する必要がありますので、復号化できないと困ります。

うまくできました。

【Laravel】【ホテル予約管理】次の課題は、自動決済を可能にする。

さて、お次の課題は、自動決済できるようにする、というものです。

具体的には、

  • クレジット情報を登録する
  • QRコードまたはナンバーを発行し、メールで送付する
  • QRコードもしくはナンバーで部屋のロックを解除し、このときに決済を行う。
  • ナンバーを3回間違えたら、ナンバーを再発行する。
  • 時間がすぎると、キャンセル料8割を決済する。

これらから、どのような実装を行うかを検討します。

クレジット情報は、ユーザーテーブルにカラムを追加するだけでいいのですが、通信はすべて暗号化する必要がありますね。

SSLが有効になるように、証明書の発行と、Webサーバの設定を変えなければいけません。

QRコードはナンバーをそのままQRコードに変換するだけで大丈夫でしょう。

幸い、LaravelにはQRコードを生成するライブラリもあるようです。

それをメールで送付しなければならないので、SMTPを使用しなければなりませんね。

QRコードの読み取りは外部のデバイスを使用する、と仮定して、実装はそのデバイスからナンバーを受け取るためのAPIを用意する必要があると思います。

あとは、コンソールコマンドを用意し、cronで定期実行できるようにすれば問題ないでしょう。

それでは次回から早速実装に入ろうと思います。

【Laravel】【ホテル予約管理】レスポンシブ対応を行う

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

さて、残る作業はレスポンシブ対応ですが、

そもそも、レスポンシブ対応とは何かというと、

PCだけでなく、スマホやタブレットでも、そのデバイスに対応した表示に切り替えることができる対応、ということです。

PCでは正しく表示できても、スマホだと表示が崩れて使用できない、というのはレスポンシブ対応とは言えません。

その確認のために重宝するのが、Google Chromeのレスポンシブモード。

擬似的にスマホやタブレットでの表示に切り替えてくれる機能です。

使い方は、Google Chromeの画面でF12を押し、

このボタンをクリックして青色(絵の状態)にします。

表示エリア上部にこんな感じのバーが表示されますので、ここをクリックすることで、画面の表示解像度を切り替えることができます。

こうやってレスポンシブ対応できているかを確認するのですね。

実機を使うと、それだけ実物のデバイスを用意しなければなりませんので、これはかなり有効な確認方法です。

ためしに、作成中のいろんなページを表示させてみましょう。

こんな感じで文字が2行になるのはまだ読めるのでセーフです。

こんな感じで表示が枠からはみ出てしまったらアウトですね。

修正する必要があります。

こういうのは、たいていテンプレートやスタイルの問題です。

こんな感じでどうでしょうか。

いい感じに仕上がったと思います。

実は、Laravelには、標準でBootstrapというJSライブラリが組み込まれていて、このライブラリでブラウザ差分を吸収したり、基本的なレスポンシブ対応を行えるようにしてくれています。

今作成している機能はBootstrapをバリバリ活用した作りになっていません。そもそもそんな表示をするところが無いためです。

もしかしたら、画像のサムネイルなどをたくさん表示させたり、表を二列に表示させる場合などに活用することになるかもしれませんね。

まぁ、これで最終チェックして提出しようと思います。

【Laravel】【ホテル予約管理】バリデーション処理を実装しなおす。

前回までの状況はこちら。

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

Vue.jsを使用する前はRequestにバリデーション処理を行っていましたが、Vue.jsを使用してからは、一切使用されていませんでした。

当然、このままではよろしくないので、パラメータをチェックするバリデーション処理を追加します。

やることは単純で、パラメータに値が入っているかどうかだけを確認します。

            validate: function(){
                var ret = true;
                this.errors = [];
                if(this.contents.id == 0) {
                    this.errors.push("ユーザーが選択されていません");
                    ret = false;
                }
                if(this.contents.num == 0) {
                    this.errors.push("人数が選択されていません");
                    ret = false;
                }
                if(this.contents.roomid == 0) {
                    this.errors.push("部屋が選択されていません");
                    ret = false;
                }
                if(this.contents.days == 0) {
                    this.errors.push("宿泊日数が入力されていません");
                    ret = false;
                }
                if(this.contents.start_day == "") {
                    this.errors.push("宿泊日が入力されていません");
                    ret = false;
                }
                if(this.contents.checkout == "") {
                    this.errors.push("チェックアウト時刻が入力されていません");
                    ret = false;
                }
                return ret;
            },
            regist: function() {
                if(this.validate() == false){
                    this.error_flg = true;
                    return;
                }
                var self = this;
                this.param.contents = this.contents;
                axios.post('/api/add', this.param).then(function(response){
                    document.location = "/management";
                }).catch(function(error){
                    self.error_flg = true;
                    self.error_message = error.response.data.errors;
                    console.log("失敗しました");
                });
            },
        <p v-if="error_flg == true" class="error">
            <ul>
                <li v-for="error in errors">{{ error }}</li>
            </ul>
        </p>

チェックに引っかかった項目をすべてerrorsにpushして、error_flg=trueとすることで、その内容をリストで表示します。

おなじ処理を編集画面にも実装します。

うまく動きました。

【Laravel】【ホテル予約管理】忘れていた機能を追加する

前回までの状況はこちら。

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

さて、ここである重大な欠陥を見つけてしまいました。

それは、Vue.jsで実装を行った際、管理者が予約をチェックする処理の実装を忘れていました。

いかんいかん、完全にミスったわ。

というわけで、ここを実装します。

修正するのは予約一覧から予約をクリックして表示される予約詳細画面です。

管理者がこの画面を表示させた場合、チェックボタンを追加し、チェック処理を行います。

Vue.jsのテンプレートと処理を追加します。

            <p>
                <button @click="closeModal">close</button>
                <button v-if="edit_flg == false" @click="onClickEdit">編集</button>
                <button v-else @click="onClickSave">保存</button>
                <button v-if="edit_flg == false" @click="onClickDelete">削除</button>
                <button v-if="role == true && edit_flg == false" @click="onClickCheck">チェック</button>
            </p>
        data() {
            return {
                role: false,
                error_message: "",
                error_flg:false,
                errors: {},
        created: function() {
            this.getRole();
            this.getRooms();
            this.getTimeList();
        },
        methods: {
            getRole: function() {
                var self = this;
                axios.post('/api/role').then(function(response){
                    self.role = response.data.role;
                }).catch(function(error){
                    console.log("失敗しました");
                });
            },

データにroleフラグを追加し、画面表示時にroleの取得を行います。

trueが返れば管理者であるということなので(ここは前回実装の動きに合わせる)、role=trueならば、チェックボタンを表示させます。

また、編集中はチェックボタンを表示させないようにします。

では、API部分の実装。

    public function check(Request $request)
    {
        $this->registerManagement->lodging($request->id);
        return response()->json(['registerLists' => $this->registerManagement->getListByMonth(
            $request->year,
            $request->month,
            $request->room,
            Auth::user()
        )]);
    }

チェックの実行を行った直後に、予約一覧を返すことで、チェック後の予約一覧を素早く表示させることができます。

最後Routeを追加。

Route::post('/api/check', 'ApiController@check');

これで機能するようになりました。

ふぅ。(汗

【Laravel】【ホテル予約管理】管理者画面の修正

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

さて、前回はユーザーログインでのあれこれを修正したのですが、それに伴って、管理者ログインでのあれもれも直す必要があります。

まずは、予約一覧画面。

ユーザーログインでは、ログインしたユーザーのもののみを表示させましたが、管理者ログインでは、全ユーザーの情報を表示する必要があります。

なので、ロール「管理者(manager)」でなければwhere句でユーザーを指定するというふうに変えなければなりません。

    public function getListByMonth($year, $month, $room, $userId)
    {
        $select = ['reserve_managements.id as id', 'users.name as name', 'users.address as address', 'users.phone as phone', 'num', 'rooms.id as roomid', 'rooms.name as room', 'days', 'checkout', 'start_day'];
        $query = ReserveManagement::select($select)
                                ->leftJoin('reserve_management_room', 'reserve_managements.id', '=', 'reserve_management_room.reserve_management_id')
                                ->leftJoin('rooms', 'reserve_management_room.room_id', '=', 'rooms.id')
                                ->leftJoin('reserve_management_user', 'reserve_managements.id', '=', 'reserve_management_user.reserve_management_id')
                                ->leftJoin('users', 'reserve_management_user.user_id', '=', 'users.id')
                                ->where('start_day', '>=', date('Y-m-d', strtotime('first day of '.$year.'-'.$month)))
                                ->where('start_day', '<=', date('Y-m-d', strtotime('last day of '.$year.'-'.$month)))
                                ->where('reserve_management_room.room_id', $room)
                                ->where('lodging', false);
        if(Gate::denies('manager')){
            $query = $query->where('users.id', $userId);
        }
        $query = $query->orderBy('start_day');
        return $query->get();
    }

こんな感じで、where()を、Gate::denies()で「管理者以外」とすれば、このwhereはユーザーログインのみに適用されます。

次は、予約追加画面。

ユーザーログインの場合は、ログイン中の名前などの表示させましたが、管理者ログインの場合は、登録されているユーザーを選択して登録しなければなりません。

ここでVue.jsの出番です。(まじか・・・)

まずはテンプレート。

基本的には、編集時のVue.jsを参考にしています。

<template>
    <div>
        <p v-if="error_flg == true" class="error">{{error_message}}</p>
        <table class="edit">
            <tbody>
                <tr>
                    <th>名前</th>
                    <td v-if=role>{{ contents.name }}<button @click="openModal">検索</button></td>
                    <td v-else>{{ contents.name }}</td>
                </tr>
                <tr>
                    <th>住所</th>
                    <td>{{ contents.address }}</td>
                </tr>
                <tr>
                    <th>電話番号</th>
                    <td>{{ contents.phone }}</td>
                </tr>
                <tr>
                    <th>人数</th>
                    <td>
                        <select v-model=contents.num>
                            <option v-for="num in nums" v-bind:value="num.value">{{ num.text }}</option>
                        </select>
                    </td>
                </tr>
                <tr>
                    <th>宿泊部屋</th>
                    <td>
                        <select v-model="contents.roomid">
                            <option v-for="room in rooms" v-bind:value="room.id">{{ room.name }}</option>
                        </select>
                    </td>
                </tr>
                <tr>
                    <th>宿泊日数</th>
                    <td><input type="number" v-model=contents.days /></td>
                </tr>
                <tr>
                    <th>宿泊日</th>
                    <td><input type="date" v-model=contents.start_day /></td>
                </tr>
                <tr>
                    <th>チェックアウト</th>
                    <td>
                        <select v-model="contents.checkout">
                            <option v-for="time in timeList" v-bind:value="time.key">{{ time.value }}</option>
                        </select>
                    </td>
                </tr>
            </tbody>
        </table>
        <p><button @click="regist">登録</button></p>
        <div id="overlay" v-show="showContent">
            <div id="content">
                <table class="edit">
                    <p>名前<input type="text" v-model=param.search /></p>
                    <tbody>
                        <tr>
                            <th>名前</th>
                            <th>住所</th>
                            <th>電話番号</th>
                        </tr>
                        <tr v-for="user in users" @click="selectUser(user)">
                            <td class="name">{{ user.name }}</td>
                            <td class="address">{{ user.address }}</td>
                            <td class="phone">{{ user.phone }}</td>
                        </tr>
                    </tbody>
                </table>
            <p>
                <button @click="closeModal">close</button>
                <button @click="searchUsers">検索</button>
            </p>
            </div>
        </div>
    </div>
</template>

まず、テーブルの部分ですが、名前、住所、電話番号以外は、編集画面をそのまま使用しました。

問題の、名前、住所、電話番号の部分ですが、管理者ログインならば、検索ボタンを設置し、それをクリックするとモーダルダイアログを表示する、という仕組みにしたいと思います。

モーダルダイアログでユーザーを検索し、その名前をクリックすると、登録画面に反映される、という動きにします。

実際の処理。

<script>
    export default {
        data() {
            return {
                role: false,
                error_message: "",
                error_flg:false,
                errors: {},
                nums: [
                    {text:'1', value:1},
                    {text:'2', value:2}
                ],
                timeList:[],
                rooms: [],
                users: [],
                showContent: false,
                param: {
                    search: "",
                },
                contents: {
                    id: 0,
                    name: "",
                    address: "",
                    phone: "",
                    num: 0,
                    roomid: 0,
                    room: "",
                    days: 0,
                    start_day: "",
                    checkout: "",
                },
            }
        },
        created: function() {
            this.getRole();
            this.getRooms();
            this.getTimeList();
        },
        methods: {
            getRole: function() {
                var self = this;
                axios.post('/api/role').then(function(response){
                    self.role = response.data.role;
                    if(self.role == false){
                        self.contents.id = response.data.user.id;
                        self.contents.name = response.data.user.name;
                        self.contents.address = response.data.user.address;
                        self.contents.phone = response.data.user.phone;
                    }
                }).catch(function(error){
                    console.log("失敗しました");
                });
            },
            searchUsers: function() {
                this.users = [];
                var self = this;
                axios.post('/api/users', this.param).then(function(response){
                    response.data.users.forEach(element => {
                        self.users.push({id:element.id, name:element.name, address:element.address, phone:element.phone});
                    });
                }).catch(function(error){
                    console.log("失敗しました");
                });
            },
            selectUser: function(user) {
                this.contents.id = user.id;
                this.contents.name = user.name;
                this.contents.address = user.address;
                this.contents.phone = user.phone;
                this.closeModal();
            },
            regist: function() {
                var self = this;
                this.param.contents = this.contents;
                axios.post('/api/add', this.param).then(function(response){
                    document.location = "/management";
                }).catch(function(error){
                    self.error_flg = true;
                    self.error_message = error.response.data.errors;
                    console.log("失敗しました");
                });
            },
            getRooms: function() {
                var self = this;
                axios.post('/api/rooms').then(function(response){
                    response.data.roomLists.forEach(element => {
                        self.rooms.push({id:element.id, name:element.name});
                    });
                }).catch(function(error){
                    console.log("失敗しました");
                });
            },
            getTimeList: function(){
                var self = this;
                axios.post('/api/timelist').then(function(response){
                    for (let [key, value] of Object.entries(response.data.timelist)){
                        self.timeList.push({key: key, value: value});
                    }
                }).catch(function(error){
                    console.log("失敗しました");
                });
            },
            openModal: function(){
                this.users = [];
                this.showContent = true;
            },
            closeModal: function(){
                this.showContent = false;
                this.edit_flg = false;
            },
        }
    }
</script>

ポイントは、ユーザーログインも管理者ログインも使用するということ。

なので、現在ログインしているアカウントのロールをgetRole()で最初に取得している、ということです。

このgetRole()の結果で表示内容を切り替えたりしてます。

そして、必要事項を入力したら、登録処理を行います。

このとき必要になるのは、ユーザーID。これも送信するパラメータに含めます。

では、API側の処理。

    public function __construct()
    {
        $this->middleware('auth');
        $this->registerManagement = new RegisterManagementRepository();
        $this->room = new RoomRepository();
        $this->user = new UserRepository();
    }
    public function role(Request $request)
    {
        return response()->json(['role' => Gate::Allows('manager'),
                                 'user' => Auth::user()]);
    }

    public function users(Request $request)
    {
        return response()->json(['users' => $this->user->search($request->search)]);
    }

    public function add(Request $request)
    {
        \Log::debug(print_r($request->contents, true));
        if($this->registerManagement->checkSchedule($request->contents["start_day"], 
                                                    $request->contents["days"], 
                                                    $request->contents["roomid"]) == false)
        {
            return response()->json([
                'errors' => "スケジュールが重複しています"
            ], 400);
        }
        $param = $this->registerManagement->getParam();
        $this->registerManagement->add([
            $param[0] => $request->contents["num"],
            $param[1] => $request->contents["days"],
            $param[2] => $request->contents["start_day"],
            $param[3] => false,
            $param[4] => date('Y-m-d H:i', strtotime($request->contents["start_day"].' + '.$request->contents["days"].' day') + $request->contents["checkout"])
        ], $request->contents["roomid"], $this->user->getUserById($request->contents["id"]));
        return response()->json();
    }
class UserRepository
{
    public function __construct()
    {

    }

    public function getUserById($id)
    {
        return User::where("id", $id)->first();
    }

    public function search($word)
    {
        $select = ['id', 'name', 'address', 'phone'];
        return User::select($select)
                    ->where('name', 'like', "%{$word}%")
                    ->get();
    }
}

ユーザー検索で使用する「あいまい検索」はこんな感じで「like」を使用します。

管理者ログイン時の動作

ユーザーログイン時の動作

これでうまく行ったみたいです。(疲れた。。。)

【Laravel】【ホテル予約管理】予約情報を変更、削除する

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

今回は、予約の編集処理と削除処理を修正します。

まず、UIですが、氏名、住所、電話番号はユーザー登録情報を使用するため、編集画面では、編集不可にします。

                    <tbody>
                        <tr>
                            <th>名前</th>
                            <td>{{ contents.name }}</td>
                        </tr>
                        <tr>
                            <th>住所</th>
                            <td>{{ contents.address }}</td>
                        </tr>
                        <tr>
                            <th>電話番号</th>
                            <td>{{ contents.phone }}</td>
                        </tr>

編集処理では、氏名、住所、電話番号のカラムはテーブルから削除しましたので、パラメータに含めないように修正します。

ログイン中のユーザー情報はAuth::user()を使用します。

予約情報の変更のみなので、ユーザーと予約情報の紐付けは変える必要ありません。

    public function update(Request $request)
    {
        \Log::debug(print_r($request->contents, true));
        if($this->registerManagement->checkScheduleForUpdate($request->contents["start_day"], 
                                                            $request->contents["days"], 
                                                            $request->contents["id"], 
                                                            $request->contents["roomid"]) == false)
        {
            \Log::debug("スケジュールが重複しています");
            return response()->json([
                'errors' => "スケジュールが重複しています"
            ], 400);
        }
        $param = $this->registerManagement->getParam();
        $this->registerManagement->updateById($request->contents["id"],
        [
            $param[0] => $request->contents["num"],
            $param[1] => $request->contents["days"],
            $param[2] => $request->contents["start_day"],
            $param[3] => false,
            $param[4] => date('Y-m-d H:i', strtotime($request->contents["start_day"].' + '.$request->contents["days"].' day') + $request->contents["checkout"])
        ], $request->contents["roomid"]);
        return response()->json(['registerLists' => $this->registerManagement->getListByMonth(
            $request->year,
            $request->month,
            $request->room,
            Auth::id()
        )]);
    }

削除処理は、予約情報の削除と同時に予約情報との紐付けを解除する必要があります。

    public function delete(Request $request)
    {
        $this->registerManagement->deleteById($request->id, Auth::user());
        return response()->json(['registerLists' => $this->registerManagement->getListByMonth(
            $request->year,
            $request->month,
            $request->room,
            Auth::user()
        )]);
    }
    public function deleteById($id, $user)
    {
        $model = $this->getItemById($id);
        $this->detachToUser($model, $user);
        $this->detachToRoom($model, $model->rooms()->first()->id);
        $this->detachToSchedule($model);
        $model->delete();
    }

これでユーザーログインでの修正は完了しました。

次は、管理者ログインでの処理を修正します。

【Laravel】【ホテル予約管理】ユーザーアカウントで予約登録

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

今回はユーザーアカウントでログインした状態で予約登録する場合を作成していきます。

予約登録画面。

今までは宿泊者の情報を入力していますが、今回の仕様変更で、現在ログインしているユーザーの情報を表示させます。

                <table class="edit">
                    <tr>
                        <th>名前</th>
                        <td>{!! $user->name !!}</td>
                    </tr>
                    <tr>
                        <th>住所</th>
                        <td>{!! $user->address !!}</td>
                    </tr>
                    <tr>
                        <th>電話番号</th>
                        <td>{!! $user->phone !!}</td>
                    </tr>
                    <tr>
                        <th>人数</th>
                        <td>{!! Form::select('num', ['1' => 1, '2' => 2]) !!}</td>
                    </tr>
                    <tr>
                        <th>宿泊部屋</th>
                        <td>{!! Form::select('room', $rooms) !!}</td>
                    </tr>
                    <tr>
                        <th>宿泊日数</th>
                        <td>{!! Form::number('days', 1) !!}</td>
                    </tr>
                    <tr>
                        <th>宿泊日</th>
                        <td>{!! Form::date('start_day', \Carbon\Carbon::now()) !!}</td>
                    </tr>
                    <tr>
                        <th>チェックアウト</th>
                        <td>{!! Form::select('checkout', $timelist) !!}</td>
                    </tr>
                </table>
    /**
     * 入力フォーム
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('register.create',
                    [
                        'user' => Auth::user(),
                        'rooms' => $this->roomRepository->getRoomList(),
                        'timelist' => $this->registerManagement->getTimeList()
                    ]
                );
    }

ログイン中のユーザー情報はAuth::user()で簡単に取り出すことができます。

予約登録処理。

まずは、バリデーションルールを修正します。

    public function rules()
    {
        return [
            'num' => 'required|numeric|digits_between:1,2',
            'room' => 'required|numeric',
            'days' => 'required|numeric|digits_between:1,4',
            'start_day' => 'required|date',
        ];
    }

登録するデータを変更します。

class RemoveCalumnReserveManagementTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('reserve_managements', function (Blueprint $table) {
            $table->dropColumn('name');
            $table->dropColumn('address');
            $table->dropColumn('phone');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('reserve_managements', function (Blueprint $table) {
            $table->string('name')->befor('num');
            $table->string('address')->after('name');
            $table->string('phone')->after('address');
        });
    }
}
class AddReserveManagementUserTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('reserve_management_user', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('reserve_management_id')
                  ->foreign('reserve_management_id')
                  ->references('id')->on('reserve_managements')
                  ->onDelete('cascade');
            $table->integer('user_id')
                  ->foreign('user_id')
                  ->references('id')->on('users')
                  ->onDelete('cascade');
            $table->timestamps();
            $table->engine = 'InnoDB';
            $table->charset = 'utf8mb4';
            $table->collation = 'utf8mb4_unicode_ci';
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('reserve_management_user');
    }
}
    private $paramNames = ['num', 'days', 'start_day', 'lodging', 'checkout'];

予約データとユーザーを結びつけます。

データベースの構成はこんな感じになります。

    public function add($param, $room, $user)
    {
        $model = new ReserveManagement;
        foreach($this->paramNames as $name)
        {
            $model->$name = $param[$name];
        }
        $model->save();
        $this->attachToRoom($model, $room);
        $this->attachToSchedule($model);
        $this->attachToUser($model, $user);
    }
    public function attachToUser($model, $user)
    {
        $model->users()->attach($user);
    }

    public function detachToUser($model, $user)
    {
        $model->users()->detach($user);
    }
    public function store(ManagementRequest $request)
    {
        if($this->registerManagement->checkSchedule($request->start_day, 
                                                    $request->days, 
                                                    $request->room) == false)
        {
            return redirect('management/create')
                        ->with(['error' => 'スケジュールが重複します'])
                        ->withInput();
        }
        $param = $this->registerManagement->getParam();
        $this->registerManagement->add([
            $param[0] => $request->num,
            $param[1] => $request->days,
            $param[2] => $request->start_day,
            $param[3] => false,
            $param[4] => date('Y-m-d H:i', strtotime($request->start_day.'+'.$request->days.' day') + $request->checkout)
        ], $request->room, Auth::user());
        return redirect('management');
    }

最後に、登録した情報を予約一覧に表示させます。

ユーザーでログインしているときは、他のユーザーの情報が見えないようにしないといけません。

    public function getList()
    {
        $select = ['reserve_managements.id as id', 'num', 'rooms.id as roomid', 'rooms.name as room', 'days', 'checkout', 'start_day'];
        return ReserveManagement::select($select)
                                    ->where('lodging', false)
                                    ->orderBy('start_day')
                                    ->leftJoin('reserve_management_room', 'reserve_managements.id', '=', 'reserve_management_room.reserve_management_id')
                                    ->leftJoin('rooms', 'reserve_management_room.room_id', '=', 'rooms.id')
                                    ->get();
    }

    public function getListByMonth($year, $month, $room, $userId)
    {
        $select = ['reserve_managements.id as id', 'users.name as name', 'users.address as address', 'users.phone as phone', 'num', 'rooms.id as roomid', 'rooms.name as room', 'days', 'checkout', 'start_day'];
        return ReserveManagement::select($select)
                                ->leftJoin('reserve_management_room', 'reserve_managements.id', '=', 'reserve_management_room.reserve_management_id')
                                ->leftJoin('rooms', 'reserve_management_room.room_id', '=', 'rooms.id')
                                ->leftJoin('reserve_management_user', 'reserve_managements.id', '=', 'reserve_management_user.reserve_management_id')
                                ->leftJoin('users', 'reserve_management_user.user_id', '=', 'users.id')
                                ->where('start_day', '>=', date('Y-m-d', strtotime('first day of '.$year.'-'.$month)))
                                ->where('start_day', '<=', date('Y-m-d', strtotime('last day of '.$year.'-'.$month)))
                                ->where('reserve_management_room.room_id', $room)
                                ->where('lodging', false)
                                ->where('users.id', $userId)
                                ->orderBy('start_day')
                                ->get();
    }
    public function registers(Request $request)
    {
        return response()->json(['registerLists' => $this->registerManagement->getListByMonth(
            $request->year,
            $request->month,
            $request->room,
            Auth::user()->id
        )]);
    }

まだ十分ではないけれど、そこそこ形になってきました。

【Laravel】【ホテル予約管理】ユーザーアカウントにユーザー情報を含める

前回までの状況はこちら

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

さて、これからどんな形にしていこうかというと、

  • ユーザー登録時にユーザー情報を入力する(フルネーム、住所、電話番号)
  • 予約登録時にはログインユーザーの情報を使用するので、ユーザー情報の入力を省く
  • 部屋ごとに利用できる(空きのある)日と、利用できない(ほかユーザーが予約済み)日をわかるようにする

というのが必要かなと。

まずは、ユーザー登録処理を修正しましょうか。

というわけで、データベースのカラムを追加します。

$ php artisan make:migration move_column_name_address_phone_to_users_table --table=users
class MoveColumnNameAddressPhoneToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('fullname')->after('email');
            $table->string('address')->after('fullname');
            $table->string('phone')->after('address');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('fullname');
            $table->dropColumn('address');
            $table->dropColumn('phone');
        });
    }
}
$ php artisan migrate

これで、usersテーブルにフルネーム、住所、電話番号のカラムが作成されました。

Viewを書き換えます。

                        <div class="form-group{{ $errors->has('fullname') ? ' has-error' : '' }}">
                            <label for="fullname" class="col-md-4 control-label">Full Name</label>

                            <div class="col-md-6">
                                <input id="fullname" type="text" class="form-control" name="fullname" value="{{ old('fullname') }}" required autofocus>

                                @if ($errors->has('fullname'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('fullname') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('address') ? ' has-error' : '' }}">
                            <label for="address" class="col-md-4 control-label">Address</label>

                            <div class="col-md-6">
                                <input id="address" type="text" class="form-control" name="address" value="{{ old('address') }}" required autofocus>

                                @if ($errors->has('address'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('address') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group{{ $errors->has('phone') ? ' has-error' : '' }}">
                            <label for="phone" class="col-md-4 control-label">phone</label>

                            <div class="col-md-6">
                                <input id="phone" type="text" class="form-control" name="phone" value="{{ old('phone') }}" required autofocus>

                                @if ($errors->has('phone'))
                                    <span class="help-block">
                                        <strong>{{ $errors->first('phone') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

register.blade.phpに上の部分を書き足しました。

登録処理も書き換えます。

app/User.phpを以下のように書き換えます。

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'fullname', 'address', 'phone', 'email', 'password',
    ];

これは、usersテーブルのカラムを設定している箇所です。

これを実際のカラム名に合わせなければ、正しくデータベースに反映されません。

次に、パラメータチェックです。

registerController.phpのバリデートルールを書き足します。

    protected function validator(array $data)
    {
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'fullname' => 'required|string|max:255',
            'address' => 'required|string|max:255',
            'phone' => 'required|digits:11',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
        ]);
    }

そして、実際にユーザーを作る処理。

    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'fullname' => $data['fullname'],
            'address' => $data['address'],
            'phone' => $data['phone'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);
    }

これで、ユーザー情報を追加することができました。

【Laravel】【ホテル予約管理】ユーザーの権限管理を行う

最新ソースはこちら(gitHub)

https://github.com/takishita2nd/hotel-mng

さて、お次の課題は、

  • 一般ユーザーからでも予約登録ができる
  • レスポンシブ対応にする

です。

で、そのために必要になるのが、ユーザーの権限管理というものです。

具体的には、管理者用のアカウントを登録して、管理者アカウントからは今までのフル機能が使用できる。

その一方で、通常ユーザーの場合は、一部の機能だけが使用できる、という感じです。

Laravelの権限管理の方法は色々あるようですが、今回は一番簡単なGATEを使用した方法を採用しようと思います。

この方法なら、新たに特別なプラグインを使用する必要がないためです。

では、Userテーブルに還元を管理するカラムroleを追加します。

$ php artisan make:migration add_column_role_users_table --table=users
class AddColumnRoleUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->tinyInteger('role')->default(0)->after('password');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('role');
        });
    }
}
$ php artisan migrate

単純にrole=0が一般ユーザー、role=1が管理者アカウントとします。

では、Seederを使用して、管理者アカウントを予め追加しておきます。

(これを行わないと、管理者権限を与えるユーザーが存在しないことになるので、運用できません!)

$ php artisan make:seeder UsersTableSeeder
class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('users')->insert([
            'name' => 'manager',
            'email' => 'manager@gmail.com',
            'password' => bcrypt('manager'),
            'role' => 1,
        ]);
    }
}
$ composer dump-autoload
$ php artisan db:seed --class=UsersTableSeeder

登録できました。

次は、AuthServiceProviderのboot()に処理を追加して、GATEによる権限の定義を記入します。

    public function boot()
    {
        $this->registerPolicies();

        Gate::define('manager', function ($user) {
            return ($user->role == 1);
        });
        Gate::define('user', function ($user) {
            return ($user->role == 0);
        });
    }

これで、role=1(manager)、role=0(user)がGATEによって定義されました。

では、Viewを書き換えて、一般ユーザーには不要なメニューを表示させないようにします。

<div class="panel-body">
    <detail-component></detail-component>
    <div>{{ Html::Link('/management/create', '追加', ['class' => 'btn']) }}</div>
</div>
@can('manager')
<div>{{ Html::link('/management/schedule', 'スケジュール', ['class' => 'btn']) }}</div>
<div>{{ Html::link('/management/total', '集計', ['class' => 'btn']) }}</div>
<div>{{ Html::link('/room', '部屋一覧へ', ['class' => 'btn']) }}</div>
<div>{{ Html::link('/management/checkout', '本日のチェックアウト', ['class' => 'btn']) }}</div>
@endcan

ポイントは@can(権限)〜@canend。

@can(権限)に権限の名前を入れることで、ユーザーの権限に対して@can(権限)〜@canendの表示を切り替えることができます。

manager権限

一般ユーザー

こんな感じで、表示の切り替えができました。

では、次回からは一般ユーザーの使い勝手がいいように修正していきましょうか。