[CakePHP] 予約管理システム

タグ :

これまでの基礎知識を結集して簡易アプリを作成するシリーズです。
今回はお店等で役立つ Web 上での予約管理システムを作成します。
改善版「予約管理システム改」が公開されています。

今回は美容院の予約をイメージして作成しました。予約にはユーザーログインが必要ですが、適当に登録しちゃって問題ありません。登録したメールアドレスには何も通達はいきませんし、何かに使用するということも全くありません。何なら存在しないアドレスでも構わないです。サンプルで作成したものですので、自由に遊んでみてください。
尚、デザインは CakePHP のデフォルトのままを使用します。

上記では操作しにくいという方は、こちら でお試しください。

アプリ概要

以下のような仕様とします。

ログイン機能を搭載
 - ユーザー登録を必要とする(メールアドレス、パスワード、名前)
 - ログインしないと予約できないものとする
 - マイページから退会できる
予約機能を搭載
 - 予約一覧ページに登録済みの予約を表示する
 - 予約追加ページから予約登録を行う
 - マイページで予約の削除ができる
 - 予約回数は無制限とする

必要となる処理は以下になります。

  • ユーザーのデータベース登録
  • ログイン認証
  • 予約のデータベース登録
  • 予約データベースからのデータ取得
  • 日付単位・ユーザー単位の予約データ取得
  • 予約データベースのデータ削除

ページ遷移は以下になります。

cake_appo_page

データテーブル

前準備としてデータベースに登録用のテーブルを作成しておきます。
作成するテーブルは以下になります。

[users]
フィールド名 名称 説明
id ユーザーID 主キー
username メールアドレス 登録するメールアドレス
password パスワード 登録するパスワード
name ユーザー名 登録する名前
[appointments]
フィールド名 名称 説明
id 予約ID 主キー
user_id ユーザーID 予約したユーザーID
order_id オーダーID 予約したオーダーID
date 日付 予約した日付
start 開始時間 予約した時間
table 席番号 予約した時に登録される席番号
[orders]
フィールド名 名称 説明
id オーダーID 主キー
order オーダー オーダー内容
time 所要時間 オーダーを要する時間
[times]
フィールド名 名称 説明
id 時刻ID 主キー
time 時刻 予約用の開始時刻

[users]と[appointments]テーブルは、登録によって随時データが追加されていくテーブルになります。
[orders]と[times]テーブルはデータが可変されない固定テーブルになります。別に無くてもいいのですが、コントローラ内でこのデータを持たせると、変更が生じた場合に面倒なので、データベースに保持するようにしました。例えば、オーダー内容に変更が合った場合、ソースコードを触らなくてもデータベースのデータのみの変更で対応できます。(プログラマーにとってはソースコードを変更した方が簡単かもしれませんが、、、)

テーブルを作成したら、Bake によって基本ファイルを自動生成します。
生成されるファイルで必要となるものは以下(赤字)になります。

上記以外のコントローラとビューのファイルは今回は使わないので削除してしまっても大丈夫です。
また、ビューの[Users]内にある「login.ctp」は新たに作成したファイルになります。「add.ctp」をコピーして作成すると簡単に作れます。

作成したサンプル

編集した CakePHP のコードは以下になります。

[ コントローラ ]
<?php
App::uses('AppController', 'Controller');
class UsersController extends AppController {
	
	public $uses = array(
				'Appointment',
				'Order'
				);
	
	public function beforeFilter() {
		// 親クラスのbeforeFilterの読み込み
		parent::beforeFilter();
		// 認証不要のページの指定
		$this->Auth->allow('login', 'add');
	}
	
	public function login() {
		// POST送信なら
		if($this->request->is('post')) {
			// ログインOKなら
			if($this->Auth->login()) {
				// Auth指定のページへ移動
				$this->redirect($this->Auth->redirect(array('controller' => 'appointments', 'action' => 'index')));
			} else { // ログインNGなら
				$this->Session->setFlash(__('Mail or Password is different.'), 'default', array(), 'auth');
				$this->set('email', $this->request->data['User']['username']);
			}
		} else {
			$this->set('email', '');
		}
	}
	
	public function logout() {
		$this->Auth->logout();
		$this->redirect(array('controller' => 'appointments', 'action' => 'index'));
	}
	
	public function view($id = null) {
		// ユーザー情報取得
		$this->User->id = $id;
		if (!$this->User->exists()) {
			throw new NotFoundException(__('Invalid user'));
		}
		$user = $this->Auth->user();
		// 予約データ取得
		$appo = $this->Appointment->find('all', array(
			'conditions' => array('user_id' => $user['id']),
			'order' => array('start' => 'ASC')
		));
		// オーダー取得
		$orders = $this->Order->find('list', array(
			'fields' => 'order'
		));
		// データ渡し
		$this->set('user', $this->User->read(null, $id));
		$this->set('appointments', $appo);
		$this->set('orders', $orders);
	}

	public function add() {
		if ($this->request->is('post')) {
			$this->request->data['User']['password'] = AuthComponent::password($this->request->data['User']['password']);
			$this->User->create();
			if ($this->User->save($this->request->data)) {
				$this->Session->setFlash(__('The user has been saved'));
				$this->redirect(array('action' => 'login'));
			} else {
				$this->Session->setFlash(__('The user could not be saved. Please, try again.'));
			}
		}
	}

	public function delete($id = null) {
		if (!$this->request->is('post')) {
			throw new MethodNotAllowedException();
		}
		$this->User->id = $id;
		if (!$this->User->exists()) {
			throw new NotFoundException(__('Invalid user'));
		}
		if ($this->User->delete()) {
			$this->Session->setFlash(__('User deleted'));
			$this->redirect(array('action' => 'login'));
		}
		$this->Session->setFlash(__('User was not deleted'));
		$this->redirect(array('action' => 'view'));
	}
}
<?php
App::uses('AppController', 'Controller');
class AppointmentsController extends AppController {
	
	public $uses = array(
				'Appointment',
				'Time',
				'Order'
				);
	
	public function beforeFilter() {
		// 親クラスのbeforeFilterの読み込み
		parent::beforeFilter();
		// 認証不要のページの指定
		$this->Auth->allow('index');
	}
	
	public function index($date_id = 0) {
		// 指定した日付データを取得
		if ($date_id) {
			$strdate = date('Y年m月d日', strtotime($date_id));
			$date = date('Y-m-d', strtotime($date_id));
			$link = date('Ymd', strtotime($date_id));
		} else {
			$strdate = date('Y年m月d日');
			$date = date('Y-m-d');
			$link = date('Ymd');
		}
		// 予約データ取得
		$appo = $this->Appointment->find('all', array(
			'conditions' => array('date' => $date),
			'order' => array('start' => 'ASC')
		));
		// ログイン状態チェック
		$user = $this->Auth->user();
		if(empty($user)){
			$this->set('str', 'Login');
			$this->set('page', 'login');
		}else{
			$this->set('str', 'MyPage');
			$this->set('page', 'view/'.$user['id']);
		}
		// タイムライン追加用クラス名生成
		for ($i = 0; $i < count($appo); $i++) {
			$appo[$i]['Appointment']['class'] = 'appo' . substr($appo[$i]['Appointment']['start'], 0, 2);
			$appo[$i]['Appointment']['height'] = $appo[$i]['Appointment']['order_id'] * 20;
			if ($appo[$i]['Appointment']['user_id'] == $user['id']) {
				$appo[$i]['Appointment']['class'] .= ' me';
				$appo[$i]['Appointment']['name'] = $appo[$i]['User']['name'];
			} else {
				$appo[$i]['Appointment']['name'] = 'Already';
			}
		}
		// データ渡し
		$this->set('appointments', $appo);
		$this->set('strdate', $strdate);
		$this->set('link', $link);
		$this->set('prev', date('Ymd', strtotime($date . ' -1 day')));
		$this->set('next', date('Ymd', strtotime($date . ' +1 day')));
	}
	
	public function add($date_id = null) {
		// 日付取得
		if ($date_id) {
			$strdate = date('Y年m月d日', strtotime($date_id));
			$date = date('Y-m-d', strtotime($date_id));
			$link = date('Ymd', strtotime($date_id));
		} else {
			$strdate = date('Y年m月d日');
			$date = date('Y-m-d');
			$link = date('Ymd');
		}
		// 時間帯取得
		$times = $this->Time->find('list', array(
			'fields' => 'time'
		));
		$i = 1;
		foreach ($times as $time) {
			$times[$i] = substr($time, 0, 5);
			$i++;
		}
		// オーダー取得
		$orders = $this->Order->find('list', array(
			'fields' => 'order'
		));
		if ($this->request->is('post')) {
			$data['Appointment']['user_id'] = $this->request->data['Appointment']['user_id'];
			$data['Appointment']['order_id'] = $this->request->data['Appointment']['order'];
			$data['Appointment']['date'] = $this->request->data['Appointment']['date'];
			$data['Appointment']['start'] = $times[$this->request->data['Appointment']['time']];
			$data['Appointment']['table'] = 1;
			// 予約済データ取得
			$appo = $this->Appointment->find('all', array(
				'conditions' => array('date' => $data['Appointment']['date']),
				'order' => array('start' => 'ASC')
			));
			// 予約済データの比較 被りがあるなら席を変更
			$flgs = array(null, true, true, true);
			foreach ($appo as $ap) {
				if (substr($ap['Appointment']['start'],0,5) == $data['Appointment']['start']
					|| ($ap['Appointment']['order_id'] >= 3 && (int)substr($ap['Appointment']['start'],0,2) == substr($data['Appointment']['start'],0,2)-1)
					|| ($data['Appointment']['order_id'] >= 3 && (int)substr($ap['Appointment']['start'],0,2)-1 == substr($data['Appointment']['start'],0,2))) {
					$flgs[$ap['Appointment']['table']] = false;
					if ($flgs[1]) {
						$data['Appointment']['table'] = 1;
					} else if ($flgs[2]) {
						$data['Appointment']['table'] = 2;
					} else if ($flgs[3]) {
						$data['Appointment']['table'] = 3;
					} else {
						$this->Session->setFlash(__('The appointment could not be saved.'));
						$this->redirect(array('action' => 'index/'.$link));
					}
				}
			}
			$this->Appointment->create();
			if ($this->Appointment->save($data)) {
				$this->Session->setFlash(__('The appointment has been saved'));
				$this->redirect(array('action' => 'index/'.$link));
			} else {
				$this->Session->setFlash(__('The appointment could not be saved. Please, try again.'));
			}
		}
		$user = $this->Auth->user();
		$this->set('user_id', $user['id']);
		$this->set('user_name', $user['name']);
		$this->set('date', $date);
		$this->set('strdate', $strdate);
		$this->set('times', $times);
		$this->set('orders', $orders);
		$this->set('link', $link);
	}
	
	public function delete($id = null) {
		if (!$this->request->is('post')) {
			throw new MethodNotAllowedException();
		}
		$this->Appointment->id = $id;
		$user = $this->Auth->user();
		if (!$this->Appointment->exists()) {
			throw new NotFoundException(__('Invalid appointment'));
		}
		if ($this->Appointment->delete()) {
			$this->Session->setFlash(__('Appointment deleted'));
			$this->redirect(array('controller' => 'users', 'action' => 'view/'.$user['id']));
		}
		$this->Session->setFlash(__('Appointment was not deleted'));
		$this->redirect(array('controller' => 'users', 'action' => 'view/'.$user['id']));
	}
}
詳しい説明は後述します。
コントローラ内の関数として、ユーザーコントローラでは「login」「logout」「view」「add」「delete」の 5 つ、予約コントローラでは「index」「add」「delete」の 3 つを作成します。
[ ビュー(ユーザー) ]
<div class="users form">
	<h2><?php echo __('Add User'); ?></h2>
	<?php
		echo $this->Form->create('User');
		echo $this->Form->input('username', array(
			'label' => __('E-mail')
		));
		echo $this->Form->input('password');
		echo $this->Form->input('name');
		echo $this->Form->end(__('Submit'));
	?>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('Login'), array('action' => 'login')); ?></li>
		<li><?php echo $this->Html->link(__('List Appointments'), array('controller' => 'appointments', 'action' => 'index')); ?> </li>
	</ul>
</div>
<div class="users view">
	<h2><?php  echo __('MyPage'); ?></h2>
	<dl>
		<dt><?php echo __('Id'); ?></dt>
		<dd>
			<?php echo h($user['User']['id']); ?>
		</dd>
		<dt><?php echo __('Name'); ?></dt>
		<dd>
			<?php echo h($user['User']['name']); ?>
		</dd>
		<dt><?php echo __('E-mail'); ?></dt>
		<dd>
			<?php echo h($user['User']['username']); ?>
		</dd>
	</dl>
	<h3>予約リスト</h3>
	<table cellpadding="0" cellspacing="0">
	<tr>
		<th><?php echo __('Date'); ?></th>
		<th><?php echo __('Order'); ?></th>
		<th><?php echo __('Start'); ?></th>
		<th class="actions"><?php echo __('Actions'); ?></th>
	</tr>
<?php foreach ($appointments as $appointment): ?>
	<tr>
		<td><?php echo h($appointment['Appointment']['date']); ?></td>
		<td><?php echo h($orders[$appointment['Appointment']['order_id']]); ?></td>
		<td><?php echo h($appointment['Appointment']['start']); ?></td>
		<td class="actions">
			<?php echo $this->Form->postLink(__('Delete'), array('controller' => 'appointments', 'action' => 'delete', $appointment['Appointment']['id']), null, __('Are you sure you want to delete # %s?', $appointment['Appointment']['id'])); ?>
		</td>
	</tr>
<?php endforeach; ?>
	</table>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('List Appointments'), array('controller' => 'appointments', 'action' => 'index')); ?> </li>
		<li><?php echo $this->Html->link(__('Logout'), array('action' => 'logout')); ?> </li>
		<li><?php echo $this->Form->postLink(__('Delete User'), array('action' => 'delete', $user['User']['id']), null, __('Are you sure you want to delete?')); ?> </li>
	</ul>
</div>
<div class="users form">
	<h2><?php echo __('Login'); ?></h2>
	<?php
		echo $this->Session->flash('auth');
		echo $this->Form->create('User');
		echo $this->Form->input('username', array(
			'label' => __('E-mail', true),
			'value' => $email
		));
		echo $this->Form->input('password', array(
			'label' => __('Password', true),
			'value' => ''
		));
	?>
<?php echo $this->Form->end(__('Login')); ?>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('Add User'), array('controller' => 'users', 'action' => 'add')); ?> </li>
		<li><?php echo $this->Html->link(__('List Appointments'), array('controller' => 'appointments', 'action' => 'index')); ?> </li>
	</ul>
</div>
Bake にて生成した ctp ファイルを編集して作成します。
「view.ctp」にはユーザー情報とユーザーが予約した情報を表示させています。
「add.ctp」と「login.ctp」には入力フォームを表示させています。「login.ctp」は「add.ctp」と内容がほとんど同じなのでコピーしてから編集を加えると簡単です。
[ ビュー(予約) ]
<div class="appointments index">
	<h2><?php echo __('Appointments'); ?></h2>
	<div class="paging">
	<?php
		echo $this->Html->link('< '.__('prev day'), array('action' => 'index/'.$prev));
		echo $this->Html->link(__('next day').' >', array('action' => 'index/'.$next));
	?>
	</div>
	<h4><?php echo $strdate . __(' appointments'); ?></h4>
	<ul class="timeline">
		<li class="time9">09:00</li>
		<li class="time10">10:00</li>
		<li class="time11">11:00</li>
		<li class="time12">12:00</li>
		<li class="time13">13:00</li>
		<li class="time14">14:00</li>
		<li class="time15">15:00</li>
		<li class="time16">16:00</li>
		<li class="time17">17:00</li>
		<li class="time18">18:00</li>
		<li class="time19">19:00</li>
	</ul>
	<p class="appo-area-p1">1番席</p>
	<div class="appo-area1">
	<?php foreach ($appointments as $appointment): ?>
		<?php if ($appointment['Appointment']['table'] == 1): ?>
			<div class="<?php echo $appointment['Appointment']['class']; ?>" style="height:<?php echo $appointment['Appointment']['height']; ?>px">
				<p><?php echo $appointment['Appointment']['name'] ?></p>
			</div>
		<?php endif; ?>
	<?php endforeach; ?>
	</div>
	<p class="appo-area-p2">2番席</p>
	<div class="appo-area2">
	<?php foreach ($appointments as $appointment): ?>
		<?php if ($appointment['Appointment']['table'] == 2): ?>
			<div class="<?php echo $appointment['Appointment']['class']; ?>" style="height:<?php echo $appointment['Appointment']['height']; ?>px">
				<p><?php echo $appointment['Appointment']['name'] ?></p>
			</div>
		<?php endif; ?>
	<?php endforeach; ?>
	</div>
	<p class="appo-area-p3">3番席</p>
	<div class="appo-area3">
	<?php foreach ($appointments as $appointment): ?>
		<?php if ($appointment['Appointment']['table'] == 3): ?>
			<div class="<?php echo $appointment['Appointment']['class']; ?>" style="height:<?php echo $appointment['Appointment']['height']; ?>px">
				<p><?php echo $appointment['Appointment']['name'] ?></p>
			</div>
		<?php endif; ?>
	<?php endforeach; ?>
	</div>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__($str), array('controller' => 'users', 'action' => $page)); ?> </li>
		<li><?php echo $this->Html->link(__('Add Appointment'), array('action' => 'add/'.$link)); ?></li>
		<li><?php echo $this->Html->link(__('Logout'), array('controller' => 'users', 'action' => 'logout')); ?> </li>
	</ul>
</div>
<div class="appointments form">
	<h2><?php echo __('Add Appointment'); ?></h2>
	<?php
		echo $this->Form->create('Appointment');
		echo '<h3>' . $user_name . ' 様</h3>';
		echo $this->Form->hidden('user_id', array(
				'value' => $user_id
		));
		echo $this->Form->hidden('date', array(
				'value' => $date
		));
		echo '<h4>' . $strdate . __(' appointments') .'</h4>';
		echo $this->Form->input('time', array(
				'type' => 'select',
				'options' => $times,
				'after' => ' 〜'
		));
		echo $this->Form->input('order', array(
				'type' => 'radio',
				'options' => $orders,
				'value' => 1
		));
		echo $this->Form->end(__('Submit'));
	?>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('MyPage'), array('controller' => 'users', 'action' => 'view/'.$user_id)); ?> </li>
		<li><?php echo $this->Html->link(__('List Appointments'), array('action' => 'index/'.$link)); ?></li>
	</ul>
</div>
#content {
	min-width: 800px;
}
ul.timeline {
	margin-top: 20px;
	list-style-type: none;
}
ul.timeline li {
	padding-top: 10px;
	margin-top: 1px; margin-left: 0px; margin-right: 0px;
	width: 60px; height: 30px;
	text-align: center; background: #ccc; cursor: default;
}
.appo-area1, .appo-area2, .appo-area3 {
	margin-top: -450px;
	width: 100px; height: 450px;
	background: #eee;
}
.appo-area1 {
	margin-left: 80px;
}
.appo-area2 {
	margin-left: 200px;
}
.appo-area3 {
	margin-left: 320px;
}
.appo-area-p1, .appo-area-p2, .appo-area-p3 {
	position: absolute;
	margin-top: -470px;
}
.appo-area-p1 {
	margin-left: 110px;
}
.appo-area-p2 {
	margin-left: 230px;
}
.appo-area-p3 {
	margin-left: 350px;
}
.appo09, .appo10, .appo11, .appo12, .appo13,
.appo14, .appo15, .appo16, .appo17, .appo18 {
	position: absolute;
	padding-top: 0px; margin-left: 10px;
	width: 80px; height: 30px;
	text-align: center; background: gray;
}
.me {
	background: blue;
}
.appo09 {
	margin-left: 10px;
}
.appo10 {
	margin-top: 41px;
}
.appo11 {
	margin-top: 82px;
}
.appo12 {
	margin-top: 123px;
}
.appo13 {
	margin-top: 164px;
}
.appo14 {
	margin-top: 205px;
}
.appo15 {
	margin-top: 246px;
}
.appo16 {
	margin-top: 287px;
}
.appo17 {
	margin-top: 328px;
}
.appo18 {
	margin-top: 369px;
}
Bake にて生成した ctp ファイルを編集して作成します。
「index.ctp」には予約表を表示させています。予約表は見やすいようにスケジュール表のような形で表示させています。表示の仕方については CSS を参照してください。
「add.ctp」には予約に必要な情報入力フォームを表示させています。

コード解説

コントローラのコードの説明をします。

[ UsersController.php の説明 ]
[ 10行目〜15行目 ログイン非認証ページの設定 ]
public function beforeFilter() {
	// 親クラスのbeforeFilterの読み込み
	parent::beforeFilter();
	// 認証不要のページの指定
	$this->Auth->allow('');
}

ログイン認証のための処理です。ユーザー関連ページでは、ログインページと新規登録ページはログインが必要ないので、allow() に記述しておきます。

[ 17行目〜31行目 ログイン処理 ]
if($this->request->is('post')) {

ログインページの「ログイン」ボタンを押されたかどうかを判定します。

if($this->Auth->login()) {

ログイン時のメールアドレスとパスワードが登録済みかどうかを判定します。

$this->redirect($this->Auth->redirect(array('controller' => 'appointments', 'action' => 'index')));

ログイン認証できた場合は予約一覧ページに遷移します。

[ 33行目〜36行目 ログアウト処理 ]
$this->Auth->logout();

ログアウトします。

[ 38行目〜58行目 マイページ処理 ]
$user = $this->Auth->user();

ログイン中のユーザー情報を取得します。

$appo = $this->Appointment->find('all', array(
	'conditions' => array('user_id' => $user['id']),
	'order' => array('start' => 'ASC')
));

ログインユーザーの予約データを取得します。取得条件にユーザーIDを指定することで予約データ内のログインユーザーの予約のみを取得することができます。

$orders = $this->Order->find('list', array(
	'fields' => 'order'
));

オーダーデータを取得して配列に格納します。このデータは一覧内のオーダー名を表示する場合に必要になります。

[ 60行目〜71行目 ユーザーの新規登録処理 ]
$this->request->data['User']['password'] = AuthComponent::password($this->request->data['User']['password']);

入力したパスワードを暗号化します。Auth 認証のために必要になります。

[ 73行目〜87行目 ユーザー削除処理 ]

処理後の遷移ページの設定以外は変更していませんので割愛します。

[ AppointmentsController.php の説明 ]
[ 18行目〜61行目 予約データの取得処理 ]
if ($date_id) {
	$strdate = date('Y年m月d日', strtotime($date_id));
	$date = date('Y-m-d', strtotime($date_id));
	$link = date('Ymd', strtotime($date_id));
} else {
	$strdate = date('Y年m月d日');
	$date = date('Y-m-d');
	$link = date('Ymd');
}

パラメータにて取得した日付データを元に表示用文字列、データ取得条件用文字列、パラメータ用文字列を生成します。

$appo = $this->Appointment->find('all', array(
	'conditions' => array('date' => $date),
	'order' => array('start' => 'ASC')
));

日付毎の予約データを取得します。取得条件に上記で生成した日付文字列を指定することでその日の予約データのみを取得することができます。

$user = $this->Auth->user();
if(empty($user)){
	$this->set('str', 'Login');
	$this->set('page', 'login');
}else{
	$this->set('str', 'MyPage');
	$this->set('page', 'view/'.$user['id']);
}

ログイン状態をチェックして、サイドバーにあるアクションボタンのページ遷移先を変更します。ログイン状態であればマイページへアクセスできるようにします。

for ($i = 0; $i < count($appo); $i++) {
	$appo[$i]['Appointment']['class'] = 'appo' . substr($appo[$i]['Appointment']['start'], 0, 2);
	$appo[$i]['Appointment']['height'] = $appo[$i]['Appointment']['order_id'] * 20;
	if ($appo[$i]['Appointment']['user_id'] == $user['id']) {
		$appo[$i]['Appointment']['class'] .= ' me';
		$appo[$i]['Appointment']['name'] = $appo[$i]['User']['name'];
	} else {
		$appo[$i]['Appointment']['name'] = 'Already';
	}
}

予約データを表示するためのスタイルとクラス名と表示文字列を変更します。
スタイルはオーダーIDに応じて高さを設定します。30分が 20px として高さを決定しています。
クラス名はログインユーザーの予約データであった場合に「me」を追加します。このクラス名を追加することで CSS で背景色が青になるように設定してあります。
表示文字列はログインユーザーの予約データであった場合にはユーザー名を、違う場合は「Already」が表示されるようにします。

[ 62行目〜132行目 予約追加処理 ]
$times = $this->Time->find('list', array(
	'fields' => 'time'
));
$i = 1;
foreach ($times as $time) {
	$times[$i] = substr($time, 0, 5);
	$i++;
}

予約追加ページの時間選択セレクターの中身を生成します。データベースの時間帯テーブルに時間帯を保持しているのでそこから取得し、配列に格納します。

$data['Appointment']['user_id'] = $this->request->data['Appointment']['user_id'];
$data['Appointment']['order_id'] = $this->request->data['Appointment']['order'];
$data['Appointment']['date'] = $this->request->data['Appointment']['date'];
$data['Appointment']['start'] = $times[$this->request->data['Appointment']['time']];
$data['Appointment']['table'] = 1;

登録用データを生成します。開始時間に関しては、オーダーIDと同じように正規化した方が良いかもしれません。。。

$flgs = array(null, true, true, true);
foreach ($appo as $ap) {
	if (substr($ap['Appointment']['start'],0,5) == $data['Appointment']['start']
		|| ($ap['Appointment']['order_id'] >= 3 && (int)substr($ap['Appointment']['start'],0,2) == substr($data['Appointment']['start'],0,2)-1)
		|| ($data['Appointment']['order_id'] >= 3 && (int)substr($ap['Appointment']['start'],0,2)-1 == substr($data['Appointment']['start'],0,2))) {
		$flgs[$ap['Appointment']['table']] = false;
		if ($flgs[1]) {
			$data['Appointment']['table'] = 1;
		} else if ($flgs[2]) {
			$data['Appointment']['table'] = 2;
		} else if ($flgs[3]) {
			$data['Appointment']['table'] = 3;
		} else {
			$this->Session->setFlash(__('The appointment could not be saved.'));
			$this->redirect(array('action' => 'index/'.$link));
		}
	}
}

席決め処理です。予約済みデータと登録データを比較して、時間帯と席に被りがある場合には別の席に移動させるようにする処理です。変数「$flgs」の true が席の空きを意味します。比較により被りがあった場合に変数「$flgs」の席番号に相当する値を false にし、さらに席番号の値を次の席番号に変更します。この処理で予約の被りを回避できます。席は 3 つまでとしているので、それ以上になると予約できないようにしています。

[ 134行目〜149行目  ]

処理後の遷移ページの設定以外は変更していませんので割愛します。

まとめ

こだわった部分といえば、予約テーブルを作成して分かり易く表示させたところと、予約の被りを回避させたところくらいです。後の処理は基礎を忠実に書いただけです。もっと改版が必要な箇所も多々ありますが、とりあえず予約はできます。

CakePHP のみで作成すると、予約する度にページ遷移が必要になります。いちいちページが切り替わってしまうと、ブラウザの戻る操作で色々と不具合が生じてしまうことがあります。できればページ遷移無しで予約ができるといいのですが、CakePHP のみでは不可能ではないでしょうか。わかりませんが。。。

ということで、次回は jQuery(Ajax) を用いてページ遷移無しの予約ができるものを作成したいと思います。

Share

  • このエントリーをはてなブックマークに追加

Comment

コメントを残す

*がついている欄は必須項目です。

  • Twitter
  • Facebook
  • Google Plus
  • RSS Feed