Инструменты пользователя

Инструменты сайта


limb3_2007_4:ru:tutorials:shop:step9

Шаг9. Работа с заказами

Итак, наши пользователи могут отсылать заказы. Теперь наша задача - расширить панель управления так, чтобы администраторы могли просматривать список заказов, а пользователи - просматривать сделанные ими заказы.

Администратор должен иметь возможность:

  • просмотреть все заказы,
  • просмотреть только заказы с определенным статусом (новый, в обработке, выполненный)
  • просмотреть информацию по заказу
  • сменить статус заказа

Просмотр и изменение статусов заказов администраторами

Шаблон admin_order/display.html

При помощи этого шаблона мы будет выводить список заказов. Страница будет также содержать небольшую форму, которая позволит указать статус заказов, которые нужно отобразить. Для вывода списка заказов, мы будем использовать статический find()-метод Order :: findForAdmin(), который мы добавим чуть позже. В принпипе, аналогичную задачу мы решали при выводе списка товаров на фронтальной части для покупателей с возможностью поиска и фильтрации.

Файл shop/template/admin_order/display.html:

<core:set title='Orders'/>
<core:WRAP file="admin_page.html" as="content">
 
<active_record:fetch using='src/model/Order' find='for_admin' target="orders" navigator='pager' order='date=DESC'/>
 
<p/><b>Filter orders:</b>
<form method="GET" id='filter_form' runat='server'>
 
<?php
$template->setChildDatasource('status_options_source', Order :: getStatusOptions());
?>
 
  <select:options_source id='status_options_source' target='status' default_value='0' default_name='Show all'/>
Filter : <select id='status' name='status' runat='server'></select>
  <input type='submit' name='filter' value="Filter" class='button'/><br/>
</form>
 
<core:include file='pager.html'/>
 
<list:list id="orders">
<table cellpadding="0" cellspacing="0" class='list'>
  <thead>
  <tr>
    <th>Date</th>
    <th>Status</th>
    <th>Summ</th>
    <th>Actions</th>
  </tr>
  </thead>
  <list:item>
  <tr class='{$Parity}'>
   <td><route_url params="action:details,id:{$id}">{$date|date:"F j, Y, G:i"}</route_url></td>
   <td>{$status_name}</td>
   <td>${$summ|number:2, '.'}</td>
   <td>
     <route_url params="action:delete,id:{$id}">Delete</route_url>
   </td>
  </tr>
  </list:item>
</table>
</list:list>
</core:wrap>

Обратите внимание вот на эти две строки:

<?php
$template->setChildDatasource('status_options_source', Order :: getStatusOptions());
?>
 
<select:options_source id='status_options_source' target='status' default_value='0' default_name='Show all'/>

При помощи php-вставки мы передаем набор данных в тег <select:options_source>. Этот тег используется для заполнения списка опций тега <select>.

При помощи выражения {$date|date:«F j, Y, G:i»} мы вывели дату оформления заказа. Примененный фильтр date позволил явно указать формат вывода даты.

Также стоит отметить использование выражения {$status_name} - для вывода значения будет использован метод getStatusName(). С подобной практикой мы уже сталкивались при реализации отображения содержимого корзины.

Изменения в классе Order

Добавим метод findForAdmin() в класс Order.

Файл shop/src/model/Order.class.php:

<?php
class Order extends lmbActiveRecord
{
  [...]
  static function findForAdmin()
  {
    $toolkit = lmbToolkit :: instance();
    $status = $toolkit->getRequest()->get('status');
 
    if(!$status)
      return lmbActiveRecord :: find('Order');
    else
      return lmbActiveRecord :: find('Order', 'status = ' . (int)$status);
  }
  [...]
}
?>

Нам также потребуется метод Order :: getStatusOptions(), который будет возвращать список статусов, которые доступны для заказов.

<?php
class Order extends lmbActiveRecord
{
  [...]
  function getStatusOptions()
  {
    return array(self :: STATUS_NEW => 'New',
                 self :: STATUS_PROCESSED => 'Processed',
                 self :: STATUS_FINISHED => 'Delivered');
  }
  [...]
}
?>

Шаблон admin_order/details.html

При помощи этого шаблона мы будем отображать содержимое конкретного заказа, а также дадим администраторам возможность устанавливать новый статус заказа:

Файл shop/template/admin_order/details.html:

<core:set title='Single order'/>
<core:WRAP file="admin_page.html" as="content">
 
<active_record:fetch using='src/model/Order' target="order" first='true'>
 <fetch:param record_id='{$#request.id}'/>
</active_record:fetch>
 
<core:datasource id="order">
<h2>Items</h2>
<list:list from='lines'>
<table cellpadding="0" cellspacing="0" class='list'>
  <thead>
  <tr>
    <th>Title</th>
    <th>Price</th>
    <th>Quantity</th>
    <th>Summ</th>
  </tr>
  </thead>
  <list:item>
  <tr class='{$Parity}'>
   <td>{$product.title}</td>
   <td>${$price|number:2, '.'}</td>
   <td>{$quantity}</td>
   <td>${$summ|number:2, '.'}</td>
  </tr>
  </list:item>
</table>
</list:list>
 
<h2>Order status</h2>
Status : {$status_name}<br/>
Address : {$address}<br/>
 
<h2>Set other order status</h2>
<form id='form_status' name='form_status' method='post' runat='server'>
  <select:options_source from='^status_options' target='status'  default_value='0' default_name='------'/>
  Status : <select id='status' name='status' runat='server'></select>
  <br/>
  <input type='submit' class='button' name='set_status' value="Set new status" class='button'/><br/>
</form>
 
<h2>Customer</h2>
Name : {$user.name}<br/>
Email : <a href='mailto:{$user.email}'>{$user.email}</a><br/>
Login : {$user.login}<br/>
 
</core:datasource>
</core:wrap>

Посмотрите, как мы вывели список позиций заказа. Мы использования атрибут from тега <list:list>. Запись вида <list:list from='lines'> приводит к вызову метода get('lines') объекта Order, который вернет коллекцию (dataset) из связанных с заказом OrderLine.

Шаблон содержит форму, которая позволит администраторам менять статус заказа.

Обратите внимание на то, как мы заполнили список доступных статусов в данном случае - мы использовали атрибут from со значением «^status_options»:

<select:options_source from='^status_options' target='status'  default_value='0' default_name='------'/>
Status : <select id='status' name='status' runat='server'></select>

Почему именно так? <form> тег является контейнером данных, поэтому нам необходимо было использовать модификатор видимости на «1-уровень выше», а status_options приведет к вызову getStatusOptions() у объекта класса Order, который был загружен при помощи тега <active_record:fetch> и передан в тег <core:datasource>.

Контроллер AdminOrderController

<?php
lmb_require('src/model/Order.class.php');
 
class AdminOrderController extends lmbController
{
  function doDisplay()
  {
    $this->setFormDatasource($this->request, 'filter_form');
  }
 
  function doDelete()
  {
    try
    {
      lmbActiveRecord :: delete('Order', 'id = ' .  $this->request->getInteger('id'));
      $this->redirect();
    }
    catch(lmbARException $e)
    {
      $this->flashError('Wrond Order ID');
      $this->redirect(array('controller' => 'admin'));
      return;
    }
  }
 
  function doDetails()
  {
    if(!$this->request->hasPost())
      return;
 
    try
    {
      $order = new Order($this->request->getInteger('id'));
    }
    catch(lmbARException $e)
    {
      $this->flashError('Wrond Order ID');
      $this->redirect(array('controller' => 'admin'));
      return;
    }
 
    $status = $this->request->get('status');
    $this->flashMessage('Order status was changed');
    $order->setStatus($status);
    $order->save();
  }
}
?>

Надеемся, что все в данном классе для вас уже знакомо и пояснений не требует.

Предварительные итоги

Покажем внешний вид страницы списка заказов в панеле управления:

}

и страницы конкретного заказа:

}

Просмотр заказов покупателями

Последний небольшой шаг - добавление возможности покупателям просматривать свои заказы.

Для этого мы добавим 2 шаблона profile/orders.html и profile/show_order.html и добавим метод doShowOrder(), где мы будем проверять, имеет ли пользователь право просматривать определенный заказ.

Шаблон profile/orders.html

Файл shop/template/profile/orders.html:

<core:set title='Your orders'/>
<core:wrap file="page.html" in="content">
 
<list:list from='#user.orders'>
<table cellpadding="0" cellspacing="0" class='list'>
  <thead>
  <tr>
    <th>#</th>
    <th>Date</th>
    <th>Summ</th>
    <th>Status</th>
  </tr>
  </thead>
  <list:item>
  <tr class='{$Parity}'>
   <route_url_set field='order_url' params='action:show_order,id:{$id}'/>
   <td><a href='{$order_url}'>{$id}</a></td>
   <td><a href='{$order_url}'>{$date|date:"F j, Y, G:i"}</a></td>
   <td>${$summ|number:2, '.'}</td>
   <td>{$status_name}</td>
  </tr>
  </list:item>
</table>
<list:default>
 You made no orders in our shop yet.
</list:default>
</list:list>
</core:wrap>

Так как объект пользователя всегда доступен в корневом контейнере данных шаблона, мы можем получить список его заказов при помощи конструкции from='#user.orders'. Помните, мы добавили в класс User связь с заказами один-ко-многим под названием orders. Именно это и позволяет нам использовать подобную конструкцию прямо в шаблоне.

Шаблон profile/show_order.html

Мы решили реализовать получение данных в данном случае обычным для большинства разработчиков способом - передать их из контроллера. Было бы слишком небезопасно давать возможность пользователям иметь возможность просмотреть содержимого любого заказа, ведь идентификатор можно легко изменить.

Файл shop/template/profile/show_order.html:

<core:set title='Order details'/>
<core:wrap file="page.html" in="content">
 
<list:list from='#order.lines'>
<table cellpadding="0" cellspacing="0" class='list'>
  <thead>
  <tr>
    <th>Title</th>
    <th>Price</th>
    <th>Quantity</th>
    <th>Summ</th>
  </tr>
  </thead>
  <list:item>
  <tr class='{$Parity}'>
   <td>{$product.title}</td>
   <td>${$price|number:2, '.'}</td>
   <td>{$quantity}</td>
   <td>${$summ|number:2, '.'}</td>
  </tr>
  </list:item>
</table>
</list:list>
</core:wrap>

Конструкция <list:list from='#order.lines'> заберет из корневого контейнера данных переменную order, которую мы поставим в шаблоне из контроллера чуть ниже, затем из order возьмет переменную lines. lines - это коллекция позиций заказа.

Изменения в контроллере ProfileController

Добавим в ProfileController метод doShowOrder(), который будет загружать указанный заказ и проверять право доступа текущего пользователя к заказу:

<?php
class ProfileController extends lmbController
{
  [...]
  function doShowOrder()
  {
    try
    {
      $order = new Order($this->request->getInteger('id'));
 
      if(!$order->belongsToUser($this->toolkit->getUser()))
      {
        $this->flashError('You can see only your orders!');
        $this->redirect('/');
      }
 
      $this->view->set('order', $order);
    }
    catch(lmbARException $e)
    {
      $this->flashError('Can\'t load order!');
      $this->redirect('/');
    }
  }
 
  [...]
}
?>

Изменения в классе Order

Теперь необходимо только добавить метод Order :: belongsToUser().

class Order extends lmbActiveRecord
{
  [...]
  function belongsToUser($user)
  {
    return ($this->getUserId() == $user->getId());
  }
  [...]
}

Далее

Пока это все, о чем мы хотели рассказать Вам о Limb3 в рамках данного примера.

У нас есть для Ваc еще один шаг: Шаг10. Рекомендации по дальнейшему изучению.

Обсуждение

Ваш комментарий. Вики-синтаксис разрешён:
   ___    _  __   __ __  __  __  _____
  / _ \  | |/_/  / //_/ / / / / / ___/
 / , _/ _>  <   / ,<   / /_/ / / (_ / 
/_/|_| /_/|_|  /_/|_|  \____/  \___/
 
limb3_2007_4/ru/tutorials/shop/step9.txt · Последние изменения: 2010/11/10 10:02 (внешнее изменение)