[CakePHP] POSレジシステム

タグ :

これまでの基礎知識を結集して簡易アプリを作成するシリーズです。
今回はお店等で使用できる POS を作ってみます。
改良版「POSレジシステム改」が公開されています。

POS とは「Point Of Sales」の略で、店舗等で商品販売時の情報を記録し、売上管理や在庫管理、その後のマーケティングに応用したりできるようにしたシステムのことです。お店側が使用するもので、お会計時に入力操作することで売上管理が行えるようになっています。
定例ですが、ユーザー登録が必要です。勝手にしちゃって結構ですし、勝手に使って貰って構いませんが、データの保存に関しては責任を負い兼ねますのでご了承ください。。。
また、過去記事「予約管理システム」でユーザー登録してくれた方は同じものを使用できます。

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

アプリ概要

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

ログイン機能を搭載
 - 店舗登録を必要とする(メールアドレス、パスワード、店舗名)
 - ログインしないと使用できないものとする
 - マイショップから退会できる
商品登録
 - 商品名、値段を編集できる
売上管理
 - 会計時の販売情報をデータベース保存する
 - 販売情報は日時、商品名、個数、値段とする
 - マイショップで売上履歴を閲覧できる
 - 月単位、日単位の売上履歴を閲覧できる

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

  • 店舗のデータベース登録
  • ログイン認証
  • 簡易レジシステム
  • 販売情報をデータベースへ登録
  • 販売履歴をデータベースから取得
  • 月単位・日単位の販売履歴を取得

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

cake_posreg_page

データテーブル

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

[users]
フィールド名 名称 説明
id ユーザーID 主キー
username メールアドレス 登録するメールアドレス
password パスワード 登録するパスワード
name 店舗名 登録する店舗名
[pos_items]
フィールド名 名称 説明
id 商品ID 主キー
user_id ユーザーID ログインユーザーのID
table 商品位置 レジページの商品テーブル位置
name 商品名 商品の名前
price 値段 商品の値段
[pos_sales]
フィールド名 名称 説明
id 販売ID 主キー
user_id ユーザーID ログインユーザーのID
date 日時 販売した日時
pos_item_id 商品ID 商品テーブルのID
value 個数 販売した商品の個数
price 値段 販売した商品の値段

上記テーブルは全て登録によって随時データが追加されていくテーブルになります。
[users]テーブルはログイン用、[pos_items]テーブルは商品管理用、[pos_sales]テーブルは売上管理用となります。

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

上記以外のコントローラとビューのファイルは今回は使わないので削除してしまっても大丈夫です。
また、[Users]の「login.ctp」「detail.ctp」、[PosItems]の「edit_item.ctp」「account.ctp」は新規に作成する必要がありますが、内容は後述します。

作成したサンプル

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

[ コントローラ(UsersController.php) ]
public function login() {
	// POST送信なら
	if($this->request->is('post')) {
		// ログインOKなら
		if($this->Auth->login()) {
			// Auth指定のページへ移動
			$this->redirect($this->Auth->redirect(array('controller' => 'pos_items', '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' => 'users', 'action' => 'login'));
}
public function view($id = null) {
	// ユーザー情報取得
	$this->User->id = $id;
	if (!$this->User->exists()) {
		throw new NotFoundException(__('Invalid user'));
	}
	$user = $this->Auth->user();
	if ($user['id'] == $id) {
		// 月の売上情報取得
		$month_page = date('Ym');
		$date_first = date('Y-m').'-01';
		$date_last = date('Y-m-t');
		$sales = $this->PosSale->find('all', array(
			'conditions' => array(
				'PosSale.user_id' => $user['id'],
				'PosSale.date >=' => $date_first,
				'PosSale.date <=' => $date_last
			)
		));
		$price_month = 0;
		foreach($sales as $sale) {
			$price_month += $sale['PosSale']['price'];
		}
		// 日の売上情報取得
		$day_page = date('Ymd');
		$date_first = date('Y-m-d');
		$date_last = date('Y-m-d', strtotime("1 day"));
		$sales = $this->PosSale->find('all', array(
			'conditions' => array(
				'PosSale.user_id' => $user['id'],
				'PosSale.date >=' => $date_first,
				'PosSale.date <=' => $date_last
			)
		));
		$price_day = 0;
		foreach($sales as $sale) {
			$price_day += $sale['PosSale']['price'];
		}
		// データ渡し
		$this->set('user', $this->User->read(null, $id));
		$this->set('price_month', $price_month);
		$this->set('price_day', $price_day);
		$this->set('month_page', $month_page);
		$this->set('day_page', $day_page);
	} else {
		$this->redirect(array('action' => 'login'));
	}
}
public function detail($date = null) {
	$user = $this->Auth->user();
	// パラメータチェック
	if (strlen($date) == 6) {
		// 文字数が 6 なら「月」
		$y = substr($date, 0, 4);
		$m = substr($date, 4);
		// 日付チェック
		if (!checkdate($m, '01', $y)) {
			$this->Session->setFlash(__('Invalid date'));
			$this->redirect(array('action' => 'view/'.$user['id']));
		}
		// データ取得期間を取得
		$date_first = date('Y-m-d', strtotime($date.'01'));
		$date_last = date('Y-m-d', mktime(0,0,0,$m+1,1,$y));
		// 前月、次月のパラメータ生成
		$this->set('page_prev', date('Ym', mktime(0,0,0,$m-1,1,$y)));
		$this->set('page_next', date('Ym', mktime(0,0,0,$m+1,1,$y)));
		$this->set('title', 'Month Detail');
		$this->set('str_prev', 'prev month');
		$this->set('str_next', 'next month');
		$this->set('str_page', 'Day Detail');
		$this->set('link_page', date('Ymd'));
	} else if (strlen($date) == 8) {
		// 文字数が 8 なら「日」
		$y = substr($date, 0, 4);
		$m = substr($date, 4, 2);
		$d = substr($date, 6);
		// 日付チェック
		if (!checkdate($m, $d, $y)) {
			$this->Session->setFlash(__('Invalid date'));
			$this->redirect(array('action' => 'view/'.$user['id']));
		}
		// データ取得期間を取得
		$date_first = date('Y-m-d', strtotime($date));
		$date_last = date('Y-m-d', mktime(0,0,0,$m,$d+1,$y));
		// 前日、次日のパラメータ生成
		$this->set('page_prev', date('Ymd', mktime(0,0,0,$m,$d-1,$y)));
		$this->set('page_next', date('Ymd', mktime(0,0,0,$m,$d+1,$y)));
		$this->set('title', 'Day Detail');
		$this->set('str_prev', 'prev day');
		$this->set('str_next', 'next day');
		$this->set('str_page', 'Month Detail');
		$this->set('link_page', date('Ym'));
	} else {
		// それ以外はNG
		$this->Session->setFlash(__('Invalid date'));
		$this->redirect(array('action' => 'view/'.$user['id']));
	}
	// 売上データ取得
	$lists = $this->PosSale->find('all', array(
		'conditions' => array(
			'PosSale.user_id' => $user['id'],
			'PosSale.date >=' => $date_first,
			'PosSale.date <=' => $date_last
		)
	));
	// 合計金額と合計個数の取得
	$total_price = $total_value = 0;
	foreach($lists as $list) {
		$total_price += $list['PosSale']['price'];
		$total_value += $list['PosSale']['value'];
	}
	$this->set('lists', $lists);
	$this->set('total_price', $total_price);
	$this->set('total_value', $total_value);
	$this->set('user_id', $user['id']);
}
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)) {
			// 商品リストを生成してデータベース登録
			$def_data = Configure::read('ga_default');
			$user = $this->User->find('all', array(
				'conditions' => array('User.username' => $this->request->data['User']['username'])
			));
			$i = 0;
			foreach($def_data as $def) {
				$item_data[$i]['PosItem']['user_id'] = $user[0]['User']['id'];
				$item_data[$i]['PosItem']['table'] = $i + 1;
				$item_data[$i]['PosItem']['name'] = $def['name'];
				$item_data[$i]['PosItem']['price'] = $def['price'];
				$i++;
			}
			$this->PosItem->saveAll($item_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'));
}
詳しい説明は後述します。
UsersController.php では「ユーザー登録」「ログイン・ログアウト」「マイショップ」「売上管理」のデータ取得・登録処理を行います。
[ コントローラ(PosItemsController.php) ]
public function index($id = null) {
	$user = $this->Auth->user();
	// 商品データを取得
	$items = $this->PosItem->find('all', array(
		'conditions' => array('user_id' => $user['id']),
		'order' => array('table' => 'ASC')
	));
	$tables = $vals = array();
	$sales = $strvals = '';
	$total['value'] = $total['price'] = 0;
	// パラメータチェック
	if ($id) {
		// パラメータ有ならお買い上げリストに追加(セッション使用)
		if ($this->request->data) {
			$this->Session->write('user_id', $user['id']);
			$this->Session->write('date', date("Y-m-d H:i:s"));
			$this->Session->write('total_value', $this->request->data['PosItem']['total_value']);
			$this->Session->write('total_price', $this->request->data['PosItem']['total_price']);
			$this->Session->write('sales', $this->request->data['PosItem']['sales']);
			$this->Session->write('vals', $this->request->data['PosItem']['vals']);
			$this->redirect(array('action' => 'account'));
		}
		$sales = $this->Session->read('pos_id').$id.',';
		$this->Session->write('pos_id', $sales);
		$subs = split(",", $sales);
		foreach($items as $item) {
			foreach($subs as $sub) {
				if ($item['PosItem']['id'] == $sub) {
					$tables[$sub]['id'] = $item['PosItem']['id'];
					$tables[$sub]['name'] = $item['PosItem']['name'];
					if (!empty($tables[$sub]['value'])) {
						$tables[$sub]['value'] += 1;
						$tables[$sub]['price'] = $item['PosItem']['price'] * $tables[$sub]['value'];
						$vals[$sub] += 1;
					} else {
						$tables[$sub]['value'] = 1;
						$tables[$sub]['price'] = $item['PosItem']['price'];
						$vals[$sub] = 1;
					}
					if (!empty($total['value'])) {
						$total['value'] += 1;
						$total['price'] += $item['PosItem']['price'];
					} else {
						$total['value'] = 1;
						$total['price'] = $item['PosItem']['price'];
					}
				}
			}
		}
		foreach($vals as $val) {
			$strvals .= $val.',';
		}
	} else {
		// パラメータ無ならセッションクリア
		$this->Session->delete('pos_id');
	}
	$this->set('posItems', $items);
	$this->set('user_id', $user['id']);
	$this->set('tables', $tables);
	$this->set('total', $total);
	$this->set('sales', $sales);
	$this->set('vals', $strvals);
}
public function delete($id = null) {
	$sales = str_replace($id.',', '', $this->Session->read('pos_id'));
	$this->Session->write('pos_id', $sales);
	$this->redirect(array('action' => 'index/-1'));
}
public function account() {
	// ポスト送信チェック
	if ($this->request->data) {
		$pos_data_id = split(",", $this->request->data['PosSale']['sales']);
		$pos_data_val = split(",", $this->request->data['PosSale']['vals']);
		$pos_data = $this->PosItem->find('all', array(
			'conditions' => array('PosItem.id' => $pos_data_id)
		));
		$i = 0;
		foreach($pos_data as $pos) {
			$data[$i]['PosSale']['user_id'] = $pos['PosItem']['user_id'];
			$data[$i]['PosSale']['date'] = $this->request->data['PosSale']['date'];
			$data[$i]['PosSale']['pos_item_id'] = $pos['PosItem']['id'];
			$data[$i]['PosSale']['value'] = $pos_data_val[$i];
			$data[$i]['PosSale']['price'] = $pos['PosItem']['price'] * $pos_data_val[$i];
			$i++;
		}
		$this->Session->delete('pos_id');
		$this->Session->delete('user_id');
		$this->Session->delete('date');
		$this->Session->delete('total_value');
		$this->Session->delete('total_price');
		$this->Session->delete('sales');
		$this->Session->delete('vals');
		$this->PosSale->create();
		if ($this->PosSale->saveAll($data)) {
			$this->Session->setFlash(__('The items sold.'));
			$this->redirect(array('action' => 'index'));
		} else {
			$this->Session->setFlash(__('Could not be sold. Please, try again.'));
		}
	}
	$this->set('user_id', $this->Session->read('user_id'));
	$this->set('date', $this->Session->read('date'));
	$this->set('total_value', $this->Session->read('total_value'));
	$this->set('total_price', $this->Session->read('total_price'));
	$this->set('sales', $this->Session->read('sales'));
	$this->set('vals', $this->Session->read('vals'));
}
public function edit() {
	$user = $this->Auth->user();
	$items = $this->PosItem->find('all', array(
		'conditions' => array('user_id' => $user['id']),
		'order' => array('table' => 'ASC')
	));
	$this->set('posItems', $items);
	$this->set('user_id', $user['id']);
}
public function edit_item($id = null) {
	$user = $this->Auth->user();
	$this->PosItem->id = $id;
	if (!$this->PosItem->exists()) {
		throw new NotFoundException(__('Invalid pos sale'));
	}
	if ($this->request->is('post')) {
		$this->request->data['PosItem']['id'] = $id;
		$this->PosItem->create();
		if ($this->PosItem->save($this->request->data)) {
			$this->Session->setFlash(__('The pos item has been saved'));
			$this->redirect(array('action' => 'edit'));
		} else {
			$this->Session->setFlash(__('The pos item could not be saved. Please, try again.'));
		}
	}
	$item = $this->PosItem->find('all', array(
		'conditions' => array('PosItem.id' => $id)
	));
	$this->set('posItem', $item);
	$this->set('user_id', $user['id']);
}
詳しい説明は後述します。
PosItemsController.php では「レジシステム」「会計」「レジ編集」のデータ取得・登録処理を行います。
[ ビュー(ユーザー) ]
<div class="users form">
	<h2><?php echo __('Add User'); ?></h2>
	<?php echo $this->Session->flash(); ?>
	<?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>
	</ul>
</div>
<div class="users view">
	<h2><?php  echo __('My Shop'); ?></h2>
	<?php echo $this->Session->flash(); ?>
	<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>
		<br>
		<dt><?php echo __('Month Sale'); ?></dt>
		<dd class="month-price"><h3><?php echo $price_month.' '.__('yen'); ?></h3></dd>
		<dt><?php echo __('Day Sale'); ?></dt>
		<dd class="day-price"><h3><?php echo $price_day.' '.__('yen'); ?></h3></dd>
	</dl>
	<h3><?php
		echo $this->Html->link(__('Detail'),
			array('action' => 'detail', $month_page),
			array('class' => 'detail-m')
		);
		echo $this->Html->link(__('Detail'),
			array('action' => 'detail', $day_page),
			array('class' => 'detail-d')
		);
	?></h3>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('Pos Items'), array('controller' => 'pos_items', 'action' => 'index')); ?> </li>
		<li><?php echo $this->Html->link(__('Edit Pos'), array('controller' => 'pos_items', 'action' => 'edit')); ?> </li>
		<li><?php echo $this->Html->link(__('Month Detail'), array('action' => 'detail/'.$month_page)); ?> </li>
		<li><?php echo $this->Html->link(__('Day Detail'), array('action' => 'detail/'.$day_page)); ?> </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(); ?>
	<?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>
	</ul>
</div>
<div class="users view">
	<h2><?php  echo __($title); ?></h2>
	<?php echo $this->Session->flash(); ?>
	<div class="paging">
	<?php
		echo $this->Html->link('< '.__($str_prev), array('action' => 'detail/'.$page_prev));
		echo $this->Html->link(__($str_next).' >', array('action' => 'detail/'.$page_next));
	?>
	</div>
	<h3><?php echo __('Total').' '.$total_price.' '.__('yen').' ( '.$total_value.' '.__('item').' )'; ?></h3>
	<table cellpadding="0" cellspacing="0">
		<tr>
			<th><?php echo __('Date'); ?></th>
			<th><?php echo __('Item'); ?></th>
			<th><?php echo __('Value'); ?></th>
			<th><?php echo __('Price'); ?></th>
		</tr>
<?php foreach ($lists as $list): ?>
		<tr>
			<td><?php echo $list['PosSale']['date']; ?></td>
			<td><?php echo $list['PosItem']['name']; ?></td>
			<td><?php echo $list['PosSale']['value']; ?></td>
			<td><?php echo $list['PosSale']['price']; ?></td>
		</tr>
<?php endforeach; ?>
	</table>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('My Shop'), array('action' => 'view/'.$user_id)); ?> </li>
		<li><?php echo $this->Html->link(__($str_page), array('action' => 'detail/'.$link_page)); ?> </li>
		<li><?php echo $this->Html->link(__('Logout'), array('action' => 'logout')); ?> </li>
	</ul>
</div>
Bake にて生成した ctp ファイルを編集して作成します。
「view.ctp」にはユーザーが登録した店舗情報と売上情報を表示させています。
「add.ctp」と「login.ctp」には入力フォームを表示させています。「login.ctp」は「add.ctp」と内容がほとんど同じなのでコピーしてから編集を加えると簡単です。
「detail.ctp」には売上データ一覧を表示させています。この一覧で売上管理っぽいことができます。
[ ビュー(レジ) ]
<div class="posItems index">
	<h2><?php echo __('Pos Items'); ?></h2>
	<ul class="item-area">
<?php foreach ($posItems as $posItem): ?>
		<li><?php echo $this->Form->postLink($posItem['PosItem']['name'], array('action' => 'index', $posItem['PosItem']['id'])); ?></li>
<?php endforeach; ?>
	</ul>
	<h4 class="sale-area-h"><?php echo __('Sale Item List'); ?></h4>
	<table class="sale-area">
<?php foreach ($tables as $table): ?>
		<tr>
			<td class="item-name"><?php echo $table['name']; ?></td>
			<td class="item-value"><?php echo $table['value']; ?></td>
			<td class="item-price"><?php echo $table['price'].__('yen'); ?></td>
			<td class="item-delete"><?php echo $this->Html->link(__('Delete'), array('action' => 'delete', $table['id'])); ?></td>
		</tr>
<?php endforeach; ?>
		<tr>
			<td class="item-name-last"><?php echo __('Total'); ?></td>
			<td class="item-value-last"><?php echo $total['value']; ?></td>
			<td class="item-price-last"><?php echo $total['price'].__('yen'); ?></td>
			<td class="item-delete"><?php echo $this->Form->postLink(__('Clear'), array('action' => 'index')); ?></td>
		</tr>
	</table>
	<?php
		echo $this->Form->create('PosItem');
		echo $this->Form->hidden('total_value',array(
			'value' => $total['value']
		));
		echo $this->Form->hidden('total_price',array(
			'value' => $total['price']
		));
		echo $this->Form->hidden('sales',array(
			'value' => $sales
		));
		echo $this->Form->hidden('vals',array(
			'value' => $vals
		));
		echo $this->Form->end(__('To Account'));
	?>
	<?php echo $this->Session->flash(); ?>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('My Shop'), array('controller' => 'users', 'action' => 'view/'.$user_id)); ?> </li>
		<li><?php echo $this->Html->link(__('Edit Pos'), array('action' => 'edit')); ?> </li>
		<li><?php echo $this->Html->link(__('Logout'), array('controller' => 'users', 'action' => 'logout')); ?> </li>
	</ul>
</div>
<div class="posItems form">
	<h2><?php echo __('Account'); ?></h2>
	<h4><?php echo $date; ?></h4>
	<h3><?php echo __('Price').' '.$total_price.' '.__('yen'); ?></h3>
	<h3><?php echo '( '.$total_value.' '.__('item').' )'; ?></h3>
	<?php
		echo $this->Form->create('PosSale');
		echo $this->Form->hidden('user_id', array(
			'value' => $user_id
		));
		echo $this->Form->hidden('date', array(
			'value' => $date
		));
		echo $this->Form->hidden('sales',array(
			'value' => $sales
		));
		echo $this->Form->hidden('vals',array(
			'value' => $vals
		));
		echo $this->Form->end(__('Receipt'));
	?>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('Pos Items'), array('action' => 'index')); ?></li>
		<li><?php echo $this->Html->link(__('Logout'), array('controller' => 'users', 'action' => 'logout')); ?> </li>
	</ul>
</div>
<div class="posItems form">
	<h2><?php echo __('Edit Pos Item'); ?></h2>
	<?php echo $this->Session->flash(); ?>
	<table cellpadding="0" cellspacing="0">
	<tr>
			<th><?php echo __('Table'); ?></th>
			<th><?php echo __('ItemName'); ?></th>
			<th><?php echo __('Price'); ?></th>
			<th class="actions"><?php echo __('Actions'); ?></th>
	</tr>
<?php foreach ($posItems as $posItem): ?>
	<tr>
		<td><?php echo h($posItem['PosItem']['table']); ?></td>
		<td><?php echo h($posItem['PosItem']['name']); ?></td>
		<td><?php echo h($posItem['PosItem']['price']); ?></td>
		<td class="actions">
			<?php echo $this->Html->link(__('Edit'), array('controller' => 'pos_items', 'action' => 'edit_item/'.$posItem['PosItem']['id'])); ?>
		</td>
	</tr>
<?php endforeach; ?>
	</table>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('My Shop'), array('controller' => 'users', 'action' => 'view/'.$user_id)); ?> </li>
		<li><?php echo $this->Html->link(__('Pos Items'), array('action' => 'index')); ?></li>
	</ul>
</div>
<div class="posItems form">
	<h2><?php echo __('Edit Pos Item'); ?></h2>
	<h3><?php echo __('Table No.').$posItem[0]['PosItem']['table']; ?></h3>
	<?php
		echo $this->Form->create('PosItem');
		echo $this->Form->input('name',array(
			'value' => $posItem[0]['PosItem']['name']
		));
		echo $this->Form->input('price',array(
			'value' => $posItem[0]['PosItem']['price']
		));
		echo $this->Form->end(__('Submit'));
	?>
</div>
<div class="actions">
	<h3><?php echo __('Actions'); ?></h3>
	<ul>
		<li><?php echo $this->Html->link(__('My Shop'), array('controller' => 'users', 'action' => 'view/'.$user_id)); ?> </li>
		<li><?php echo $this->Html->link(__('Pos Items'), array('action' => 'index')); ?></li>
		<li><?php echo $this->Html->link(__('Edit Pos'), array('action' => 'edit')); ?> </li>
	</ul>
</div>
#content {
	min-width: 800px;
}
ul.item-area {
	list-style-type: none;
	width: 305px;
}
ul.item-area li {
	float: left;
	padding-top: 10px;
	margin-top: 1px; margin-left: 1px; margin-right: 0px;
	width: 100px; height: 40px;
	text-align: center; background: #ccc; cursor: default;
}
ul.item-area li:hover {
	background: red;
}
h4.sale-area-h {
	position: absolute;
	padding-left: 350px;
}
table.sale-area {
	position: absolute;
	width: 300px;
	margin-top: 30px; margin-left: 330px;
}
table.sale-area td.item-name {
	width: 120px;
	text-align: center;
}
table.sale-area td.item-value {
	width: 20px;
	text-align: center;
}
table.sale-area td.item-price {
	width: 80px;
	text-align: right;
}
table.sale-area td.item-delete {
	width: 80px;
	text-align: center;
}
table.sale-area td.item-name-last {
	width: 120px;
	text-align: center;
	border: 0px; background: #eee;
}
table.sale-area td.item-value-last {
	width: 50px;
	text-align: center;
	border: 0px; background: #eee;
}
table.sale-area td.item-price-last {
	width: 80px;
	text-align: right;
	border: 0px; background: #eee;
}
form#PosItemIndexForm .submit {
	position: absolute;
	width: 250px;
	margin-top: -280px; margin-left: 480px;
}
Bake にて生成した ctp ファイルを編集して作成します。
「index.ctp」にはレジ画面を表示させています。商品部分は商品名をクリックできるようにし、クリックされた商品を右の一覧に表示させるようにしています。表示については CSS を参照してください。
「account.ctp」は会計ページとなりますので、会計値段、商品数、日付のみの表示です。登録に必要なフォームも表示はさせていませんが、設置します。
「edit.ctp」と「edit_item.ctp」は商品の編集ページとなります。もうちょっと編集し易くできると思いますが、サブ機能的な部分なので、とりあえずこんな感じで。。。

コード解説

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

[ UsersController.php の説明 ]
UsersController.php は過去記事「予約管理システム」を流用した部分が多いので、変更箇所のみを解説します。大きな変更箇所は「view」「detail」「add」になります。
[ view – 3行目〜7行目 ユーザー情報取得 ]
$this->User->id = $id;
if (!$this->User->exists()) {
	throw new NotFoundException(__('Invalid user'));
}
$user = $this->Auth->user();

ユーザー情報を取得します。マイショップページ表示時にパラメータとしてユーザーIDを持たせますが、このIDがユーザー登録に無いIDであった場合に不正なIDとしてページ表示を拒否するようにしています。

[ view – 8行目 ユーザー判定 ]
if ($user['id'] == $id) {

表示するマイショップのユーザーIDとログイン中のユーザーIDを比較して一致した場合のみページ表示を許可するようにしています。一致しなかった場合にはログインページに遷移するようにしています。

[ view – 9行目〜23行目 月の売上情報取得 ]
$month_page = date('Ym');
$date_first = date('Y-m').'-01';
$date_last = date('Y-m-t');
$sales = $this->PosSale->find('all', array(
	'conditions' => array(
		'PosSale.user_id' => $user['id'],
		'PosSale.date >=' => $date_first,
		'PosSale.date <=' => $date_last
	)
));

月単位の売上データを取得します。
変数「$month_page」は詳細ページへのリンク用の日付を格納します。
変数「$date_first」「$date_last」にはそれぞれデータ取得の開始日と終了日を格納します。[ date(‘Y-m-t’) ]と記述すると、現在の月の最終日が格納されます。例えば今月が 5 月だった場合には、「2013-5-31」が格納されます。便利ですな。
上記日付変数を使って、find の conditions にて取得条件を指定します。これで今月のログインユーザーの売上データのみを取得することができます。

$price_month = 0;
foreach($sales as $sale) {
	$price_month += $sale['PosSale']['price'];
}

表示用の月の売上金額の合計を算出します。
foreach ループにより取得した月の売上データから値段を加算していき、合計金額を変数「$price_month」に格納します。

[ view – 24行目〜38行目 日の売上情報取得 ]
$day_page = date('Ymd');
$date_first = date('Y-m-d');
$date_last = date('Y-m-d', strtotime("1 day"));
$sales = $this->PosSale->find('all', array(
	'conditions' => array(
		'PosSale.user_id' => $user['id'],
		'PosSale.date >=' => $date_first,
		'PosSale.date <=' => $date_last
	)
));

日単位の売上データを取得します。
変数「$day_page」は詳細ページへのリンク用の日付を格納します。
変数「$date_first」「$date_last」にはそれぞれデータ取得の開始日と終了日を格納します。[ date(‘Y-m-d’, strtotime(“1 day”)) ]と記述すると、現在日の次日が格納されます。例えば今日が 5 月 26 日だった場合には、「2013-5-27」が格納されます。
上記日付変数を使って、find の conditions にて取得条件を指定します。取得時の最終日の指定では時間を省略すると、「00:00:00」となるので、今日のログインユーザーの売上データのみを取得することができます。

$price_day = 0;
foreach($sales as $sale) {
	$price_day += $sale['PosSale']['price'];
}

表示用の日の売上金額の合計を算出します。
foreach ループにより取得した日の売上データから値段を加算していき、合計金額を変数「$price_day」に格納します。

[ detail – 4行目 パラメータチェック ]
if (strlen($date) == 6) {

取得したパラメータに応じてページ表示を切り替えます。
売上詳細ページを表示時に必ずパラメータを持たせますが、そのパラメータの文字数が 6 文字なら月の売上ページへ、8 文字なら日の売上ページへ、それ以外なら不正な日付指定ということでマイショップページに戻されるようにしています。

[ detail – 6行目〜23行目 月の売上一覧データ取得準備 ]
$y = substr($date, 0, 4);
$m = substr($date, 4);

日付を年月に分けて変数に格納します。
パラメータの文字列を用いて年と月の文字列をそれぞれ取得します。

if (!checkdate($m, '01', $y)) {
	$this->Session->setFlash(__('Invalid date'));
	$this->redirect(array('action' => 'view/'.$user['id']));
}

その日付が存在するのかをチェックします。
日付チェックには関数「checkdate()」を用います。引数に「月、日、年」の順で渡してやることで、存在する日付なら true 、不正な日付なら false が返ってきます。不正な日付だった場合は、マイショップページに戻すようにしています。

$date_first = date('Y-m-d', strtotime($date.'01'));
$date_last = date('Y-m-d', mktime(0,0,0,$m+1,1,$y));

データ取得用の開始日と終了日を取得します。
前述したのと同じように変数「$date_first」には月の一日が、変数「$date_last」には月の最終日が格納されます。

[ detail – 26行目〜44行目 日の売上一覧データ取得準備 ]
$y = substr($date, 0, 4);
$m = substr($date, 4, 2);
$d = substr($date, 6);

日付を年月日に分けて変数に格納します。
パラメータの文字列を用いて年と月と日の文字列をそれぞれ取得します。

if (!checkdate($m, $d, $y)) {
	$this->Session->setFlash(__('Invalid date'));
	$this->redirect(array('action' => 'view/'.$user['id']));
}

その日付が存在するのかをチェックします。
前述と同じです。

$date_first = date('Y-m-d', strtotime($date));
$date_last = date('Y-m-d', mktime(0,0,0,$m,$d+1,$y));

データ取得用の開始日と終了日を取得します。
同じように変数「$date_first」には現在日が、変数「$date_last」には現在日の次日が格納されます。

[ detail – 50行目〜57行目 売上データ取得 ]
$lists = $this->PosSale->find('all', array(
	'conditions' => array(
		'PosSale.user_id' => $user['id'],
		'PosSale.date >=' => $date_first,
		'PosSale.date <=' => $date_last
	)
));

データベースから売上データを取得します。
上記の日付変数を使用して条件指定することで、月か日の売上データを取得できます。

[ detail – 58行目〜63行目 合計金額と合計個数の取得 ]
$total_price = $total_value = 0;
foreach($lists as $list) {
	$total_price += $list['PosSale']['price'];
	$total_value += $list['PosSale']['value'];
}

一覧の上に表示する合計金額と合計個数を算出します。
取得した売上データの foreach ループによりそれぞれのデータを加算していくことで求めます。

[ add – 7行目〜20行目 商品リストを生成してデータベース登録 ]
$def_data = Configure::read('ga_default');

初期の商品データをデータベースに登録します。
初期のデータを定数として用意しておき、そのデータをデータベースのテーブル「pos_items」に保存させます。
初期の定数データはファイル「Config/bootstrap.php」へ以下のように定義しておきます。

Configure::write('ga_default', array(
	1 => array('name' => '商品A','price' => 100),
	2 => array('name' => '商品B','price' => 200),
	3 => array('name' => '商品C','price' => 300),
	4 => array('name' => '商品D','price' => 400),
	5 => array('name' => '商品E','price' => 500),
	6 => array('name' => '商品F','price' => 600),
	7 => array('name' => '商品G','price' => 700),
	8 => array('name' => '商品H','price' => 800),
	9 => array('name' => '商品I','price' => 900),
	10 => array('name' => '商品J','price' => 1000),
	11 => array('name' => '商品K','price' => 1100),
	12 => array('name' => '商品L','price' => 1200),
	13 => array('name' => '商品M','price' => 1300),
	14 => array('name' => '商品N','price' => 1400),
	15 => array('name' => '商品O','price' => 1500),
));
このデータを変数配列「$def_data」に格納して使用します。

$user = $this->User->find('all', array(
	'conditions' => array('User.username' => $this->request->data['User']['username'])
));

ユーザー情報を取得します。
ユーザー登録した直後ですが、既にデータベースに登録されているはずなので、登録ユーザー情報をデータベースから取得します。必要なデータはユーザーIDのみです。このユーザーIDは商品登録時に必要になります。

$i = 0;
foreach($def_data as $def) {
	$item_data[$i]['PosItem']['user_id'] = $user[0]['User']['id'];
	$item_data[$i]['PosItem']['table'] = $i + 1;
	$item_data[$i]['PosItem']['name'] = $def['name'];
	$item_data[$i]['PosItem']['price'] = $def['price'];
	$i++;
}

登録用の商品データを生成します。
データベース登録に必要なデータを変数配列「$item_data」に格納します。

$this->PosItem->saveAll($item_data);

商品データをデータベース登録します。
データベースのテーブル「pos_items」に商品データを保存します。データは複数件なので関数「saveAll()」を使用します。

[ PosItemsController.php の説明 ]
[ index – 4行目〜7行目 商品データを取得 ]
$items = $this->PosItem->find('all', array(
	'conditions' => array('user_id' => $user['id']),
	'order' => array('table' => 'ASC')
));

データベースから商品データを取得します。
取得した商品データは店舗レジページの商品の部分を表示するのに使用します。取得時の順番を table の昇順で指定することで、表示でもその順番になります。

[ index – 12行目 パラメータチェック ]
if ($id) {

店舗レジページ表示時のパラメータをチェックします。
パラメータが有る場合は、お買い上げリストへの商品追加処理を実行し、無い場合にはお買い上げリストをクリアするようにしています。ページ内の商品名のクリックにより同ページへパラメータを付加してページ遷移させています。
お買い上げリストのデータは全てセッションによるデータを元に表示させています。よって、パラメータにはセッションに追加するデータ(商品id)を付加させています。

[ index – 14行目〜22行目 会計ページ遷移チェック ]
if ($this->request->data) {
	$this->Session->write('user_id', $user['id']);
	$this->Session->write('date', date("Y-m-d H:i:s"));
	$this->Session->write('total_value', $this->request->data['PosItem']['total_value']);
	$this->Session->write('total_price', $this->request->data['PosItem']['total_price']);
	$this->Session->write('sales', $this->request->data['PosItem']['sales']);
	$this->Session->write('vals', $this->request->data['PosItem']['vals']);
	$this->redirect(array('action' => 'account'));
}

ページ内の「会計へ」をクリックされたかをチェックします。
「会計へ」をクリックするとページ内の非表示フォームのポスト送信を行うようにしているので、ポスト送信データの有無を確認することでクリックされたかを確認できます。
パラメータが有、且つポスト送信されている場合のみ会計ページへ遷移させます。パラメータが無い状態というのは、お買い上げリストがクリアされている状態なので会計ページには遷移できないようにしています。
ページ遷移時のデータの受け渡しはセッションを用いています。ページ内の非表示フォームのデータを全てセッションに保存しておくことで会計ページでも使用できるようにしています。

[ index – 23行目〜52行目 お買い上げリスト追加処理 ]
$sales = $this->Session->read('pos_id').$id.',';
$this->Session->write('pos_id', $sales);

お買い上げリスト表示に必要な商品IDの追加処理を行います。
セッションに商品IDを文字列として保存させています。商品追加には、まずセッションから商品IDの文字列を取得し、追加する商品IDを付加して再度セッションに保存します。「1,4,10,」のような文字列が保存されます。

$subs = split(",", $sales);
foreach($items as $item) {
	foreach($subs as $sub) {
		if ($item['PosItem']['id'] == $sub) {

お買い上げリストに表示する商品情報を取得します。
セッションの商品ID文字列を変数配列「$subs」に格納し、foreach ループにて全ての商品IDとリストに表示する商品IDを比較します。

$tables[$sub]['id'] = $item['PosItem']['id'];
$tables[$sub]['name'] = $item['PosItem']['name'];
/* 〜 省略 〜 */

お買い上げリストへ表示用のデータを取得します。
上記の処理でIDが一致した場合にその商品情報を変数配列「$table」に格納しておくことでリストデータを取得します。リストデータには「ID」「商品名」「個数」「値段」を用います。

[ delete – 2行目〜3行目 お買い上げリストからの削除 ]
$sales = str_replace($id.',', '', $this->Session->read('pos_id'));
$this->Session->write('pos_id', $sales);

お買い上げリストから商品を削除します。
パラメータに持たせた商品IDをセッションから削除することでリストからも削除されるようにしています。リストの各商品の右にある「削除」をクリックすることで動作します。
削除後には残りの商品のリストも表示させる必要があるので、店舗レジページ遷移時のパラメータに「-1」を付加させています。

[ account – 3行目 ポスト送信チェック ]
if ($this->request->data) {

会計ページ内の「登録」ボタンがクリックされたかをチェックします。
レジページからのページ遷移ではポスト送信では無いので、会計情報を表示するだけです。
表示以外にも、セッションデータを非表示フォームへコピーする処理も行います。

[ account – 4行目〜8行目 商品データの取得 ]
$pos_data_id = split(",", $this->request->data['PosSale']['sales']);
$pos_data_val = split(",", $this->request->data['PosSale']['vals']);
$pos_data = $this->PosItem->find('all', array(
	'conditions' => array('PosItem.id' => $pos_data_id)
));

会計登録に必要な商品データを取得します。
ページ内のフォームにセッションで取得した会計商品のIDが保存されているので、そのIDを取得時の条件で指定させることで、会計する商品データのみを取得することができます。

[ account – 9行目〜17行目 登録用データを生成 ]
$i = 0;
foreach($pos_data as $pos) {
	$data[$i]['PosSale']['user_id'] = $pos['PosItem']['user_id'];
	$data[$i]['PosSale']['date'] = $this->request->data['PosSale']['date'];
	$data[$i]['PosSale']['pos_item_id'] = $pos['PosItem']['id'];
	$data[$i]['PosSale']['value'] = $pos_data_val[$i];
	$data[$i]['PosSale']['price'] = $pos['PosItem']['price'] * $pos_data_val[$i];
	$i++;
}

データベース登録用のデータを生成します。
上記で取得した商品情報とポスト送信にて取得した情報を元に登録用データを変数配列「$data」に格納します。

[ account – 18行目〜24行目 セッションクリア ]
$this->Session->delete('pos_id');
$this->Session->delete('user_id');
$this->Session->delete('date');
$this->Session->delete('total_value');
$this->Session->delete('total_price');
$this->Session->delete('sales');
$this->Session->delete('vals');

セッションデータを全てクリアします。
会計後はお買い上げリストもクリアしたいので、セッションをクリアすることで初期状態に戻ります。

[ account – 26行目〜31行目 売上データの登録 ]
if ($this->PosSale->saveAll($data)) {

売上データを登録します。
上記で生成した変数配列「$data」をデータベーステーブル「pos_sales」へ保存します。データが複数件あるので関数「saveAll()」を用いています。

「edit」と「edit_item」では、基本的な事しか行っていないので割愛します。

まとめ

やはり CakePHP だけでは使いづらくなってしまいます。商品を追加する度にいちいちページ遷移してしまうのはカッコ悪いですね。。。

難しいことをやっていないので、作るのは簡単ですが、この程度のレベルのものになってしまいます。。。もっと使い易いものにするには、高度なデータテーブル作成技術などが必要ですが、そんな技術は持っていないので jQuery で実現したいと思います。

次回は jQuery を用いてページ遷移の少ないものを作成したいと思います。その他にも色々と改良が必要な箇所が多くありますのでかなり時間が掛かりそうですが、、、

Share

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

Comment

コメントを残す

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

  • Twitter
  • Facebook
  • Google Plus
  • RSS Feed