Магазин на Laravel 7, часть 17. Панель управления, работа с заказами, изменение статуса
25.10.2020
Теги: Laravel • MySQL • PHP • Web-разработка • БазаДанных • Заказ • ИнтернетМагазин • КаталогТоваров • ПанельУправления • Практика • Фреймворк • ШаблонСайта
Администратор магазина должен иметь возможность просматривать заказы, оформленные в магазине и как-то их обрабатывать. Для начала организуем возможность просмотра списка заказов и отдельного заказа. Обработка заказа подразумевает изменение статуса заказа, так что добавим в таблицу orders
поле status
. Чтобы изменять значение этого поля, нужна возможность редактирования заказа.
Контроллер OrderController
Создаем ресурсный контроллер:
> php artisan make:controller Admin/OrderController --resource --model=Models/Order
namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Models\Order; use Illuminate\Http\Request; class OrderController extends Controller { /** * Просмотр списка заказов * * @return \Illuminate\Http\Response */ public function index() { $orders = Order::orderBy('created_at', 'desc')->paginate(5); return view('admin.order.index',compact('orders')); } /** * Просмотр отдельного заказа * * @param \App\Models\Order $order * @return \Illuminate\Http\Response */ public function show(Order $order) { return view('admin.order.show', compact('order')); } /** * Форма редактирования заказа * * @param \App\Models\Order $order * @return \Illuminate\Http\Response */ public function edit(Order $order) { return view('admin.order.edit', compact('order')); } /** * Обновляет заказ покупателя * * @param \Illuminate\Http\Request $request * @param \App\Models\Order $order * @return \Illuminate\Http\Response */ public function update(Request $request, Order $order) { $order->update($request->all()); return redirect() ->route('admin.order.show', ['order' => $order->id]) ->with('success', 'Заказ был успешно обновлен'); } }
Маршруты для заказов
Добавляем маршруты в файле roure/web.php
:
Route::group([ 'as' => 'admin.', // имя маршрута, например admin.index 'prefix' => 'admin', // префикс маршрута, например admin/index 'namespace' => 'Admin', // пространство имен контроллера 'middleware' => ['auth', 'admin'] // один или несколько посредников ], function () { // главная страница панели управления Route::get('index', 'IndexController')->name('index'); // CRUD-операции над категориями каталога Route::resource('category', 'CategoryController'); // CRUD-операции над брендами каталога Route::resource('brand', 'BrandController'); // CRUD-операции над товарами каталога Route::resource('product', 'ProductController'); // доп.маршрут для показа товаров категории Route::get('product/category/{category}', 'ProductController@category') ->name('product.category'); // просмотр и редактирование заказов Route::resource('order', 'OrderController', ['except' => [ 'create', 'store', 'destroy' ]]); });
Маршруты для создания и удаления заказа нам не нужны, поэтому используем третий параметр Route::resource()
, чтобы их исключить.
Шаблоны для заказов
Шаблон views/admin/order/index.blade.php
для просмотра списка заказов:
@extends('layout.admin', ['title' => 'Все заказы']) @section('content') <h1>Все заказы</h1> <table class="table table-bordered"> <tr> <th>№</th> <th width="18%">Дата и время</th> <th width="18%">Покупатель</th> <th width="18%">Адрес почты</th> <th width="18%">Номер телефона</th> <th width="18%">Пользователь</th> <th><i class="fas fa-eye"></i></th> <th><i class="fas fa-edit"></i></th> </tr> @foreach($orders as $order) <tr> <td>{{ $order->id }}</td> <td>{{ $order->created_at->format('d.m.Y H:i') }}</td> <td>{{ $order->name }}</td> <td><a href="mailto:{{ $order->email }}">{{ $order->email }}</a></td> <td>{{ $order->phone }}</td> <td> @isset($order->user) {{ $order->user->name }} @endisset </td> <td> <a href="{{ route('admin.order.show', ['order' => $order->id]) }}"> <i class="far fa-eye"></i> </a> </td> <td> <a href="{{ route('admin.order.edit', ['order' => $order->id]) }}"> <i class="far fa-edit"></i> </a> </td> </tr> @endforeach </table> {{ $orders->links() }} @endsection
Шаблон views/admin/order/edit.blade.php
для редактирования заказа:
@extends('layout.admin', ['title' => 'Редактирование заказа']) @section('content') <h1 class="mb-4">Редактирование заказа</h1> <form method="post" action="{{ route('admin.order.update', ['order' => $order->id]) }}"> @csrf @method('PUT') <div class="form-group"> <input type="text" class="form-control" name="name" placeholder="Имя, Фамилия" required maxlength="255" value="{{ old('name') ?? $order->name ?? '' }}"> </div> <div class="form-group"> <input type="email" class="form-control" name="email" placeholder="Адрес почты" required maxlength="255" value="{{ old('email') ?? $order->email ?? '' }}"> </div> <div class="form-group"> <input type="text" class="form-control" name="phone" placeholder="Номер телефона" required maxlength="255" value="{{ old('phone') ?? $order->phone ?? '' }}"> </div> <div class="form-group"> <input type="text" class="form-control" name="address" placeholder="Адрес доставки" required maxlength="255" value="{{ old('address') ?? $order->address ?? '' }}"> </div> <div class="form-group"> <textarea class="form-control" name="comment" placeholder="Комментарий" maxlength="255" rows="2">{{ old('comment') ?? $order->comment ?? '' }}</textarea> </div> <div class="form-group"> <button type="submit" class="btn btn-success">Сохранить</button> </div> </form> @endsection
Шаблон views/admin/order/show.blade.php
для просмотра заказа:
@extends('layout.admin', ['title' => 'Просмотр заказа']) @section('content') <h1>Данные по заказу № {{ $order->id }}</h1> <h3 class="mb-3">Состав заказа</h3> <table class="table table-bordered"> <tr> <th>№</th> <th>Наименование</th> <th>Цена</th> <th>Кол-во</th> <th>Стоимость</th> </tr> @foreach($order->items as $item) <tr> <td>{{ $loop->iteration }}</td> <td>{{ $item->name }}</td> <td>{{ number_format($item->price, 2, '.', '') }}</td> <td>{{ $item->quantity }}</td> <td>{{ number_format($item->cost, 2, '.', '') }}</td> </tr> @endforeach <tr> <th colspan="4" class="text-right">Итого</th> <th>{{ number_format($order->amount, 2, '.', '') }}</th> </tr> </table> <h3 class="mb-3">Данные покупателя</h3> <p>Имя, фамилия: {{ $order->name }}</p> <p>Адрес почты: <a href="mailto:{{ $order->email }}">{{ $order->email }}</a></p> <p>Номер телефона: {{ $order->phone }}</p> <p>Адрес доставки: {{ $order->address }}</p> @isset ($order->comment) <p>Комментарий: {{ $order->comment }}</p> @endisset @endsection
Временна́я зона
Есть проблема с показом времени заказа при просмотре списка всех заказов. Это потому, что в базе данных created_at
и updated_at
хранятся в UTC. Давайте добавим accessor в модель Order
и будем преобразовывать в московское время.
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Carbon; class Order extends Model { /** * Преобразует дату и время создания заказа из UTC в Europe/Moscow * * @param $value * @return \Carbon\Carbon|false */ public function getCreatedAtAttribute($value) { return Carbon::createFromFormat('Y-m-d H:i:s', $value)->timezone('Europe/Moscow'); } /** * Преобразует дату и время обновления заказа из UTC в Europe/Moscow * * @param $value * @return \Carbon\Carbon|false */ public function getUpdatedAtAttribute($value) { return Carbon::createFromFormat('Y-m-d H:i:s', $value)->timezone('Europe/Moscow'); } }
Статус заказа
Создадим миграцию, чтобы добавить поле status
в таблицу базы данных orders
.
> php artisan make:migration alter_orders_table --table=orders
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class AlterOrdersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('orders', function (Blueprint $table) { $table->unsignedTinyInteger('status') ->after('amount') ->nullable(false) ->default(0); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('orders', function (Blueprint $table) { $table->dropColumn('status'); }); } }
> php artisan migrate
Статусов у нас будет пять, добавим их в модель Order
. Заодно добавим поле status
в массив $fillable
.
class Order extends Model { protected $fillable = [ 'user_id', 'name', 'email', 'phone', 'address', 'comment', 'amount', 'status', ]; public const STATUSES = [ 0 => 'Новый', 1 => 'Обработан', 2 => 'Оплачен', 3 => 'Доставлен', 4 => 'Завершен', ]; /* ... */ }
Будем передавать во все шаблоны возможные значения статуса заказа:
class OrderController extends Controller { /* ... */ public function index() { $orders = Order::orderBy('status', 'asc')->paginate(5); $statuses = Order::STATUSES; return view('admin.order.index',compact('orders', 'statuses')); } /* ... */ public function show(Order $order) { $statuses = Order::STATUSES; return view('admin.order.show', compact('order', 'statuses')); } /* ... */ public function edit(Order $order) { $statuses = Order::STATUS; return view('admin.order.edit', compact('order', 'statuses')); } /* ... */ }
И во всех шаблонах будем показывать статус заказа. Обратите внимание, что список заказов сортируется теперь по статусу. Наверху будут новые заказы, а внизу — уже завершенные. Те заказы, которые требуют внимания администратора, всегда на виду.
@extends('layout.admin', ['title' => 'Все заказы']) @section('content') <h1>Все заказы</h1> <table class="table table-bordered"> <tr> <th>№</th> <th width="18%">Дата и время</th> <th width="5%">Статус</th> <th width="18%">Покупатель</th> <th width="18%">Адрес почты</th> <th width="18%">Номер телефона</th> <th width="18%">Пользователь</th> <th><i class="fas fa-eye"></i></th> <th><i class="fas fa-edit"></i></th> </tr> @foreach($orders as $order) <tr> <td>{{ $order->id }}</td> <td>{{ $order->created_at->format('d.m.Y H:i') }}</td> <td> @if ($order->status == 0) <span class="text-danger">{{ $statuses[$order->status] }}</span> @elseif (in_array($order->status, [1,2,3])) <span class="text-success">{{ $statuses[$order->status] }}</span> @else {{ $statuses[$order->status] }} @endif </td> <td>{{ $order->name }}</td> <td><a href="mailto:{{ $order->email }}">{{ $order->email }}</a></td> <td>{{ $order->phone }}</td> <td> @isset($order->user) {{ $order->user->name }} @endisset </td> <td> <a href="{{ route('admin.order.show', ['order' => $order->id]) }}"> <i class="far fa-eye"></i> </a> </td> <td> <a href="{{ route('admin.order.edit', ['order' => $order->id]) }}"> <i class="far fa-edit"></i> </a> </td> </tr> @endforeach </table> {{ $orders->links() }} @endsection
@extends('layout.admin', ['title' => 'Просмотр заказа']) @section('content') <h1>Данные по заказу № {{ $order->id }}</h1> <p> Статус заказа: @if ($order->status == 0) <span class="text-danger">{{ $statuses[$order->status] }}</span> @elseif (in_array($order->status, [1,2,3])) <span class="text-success">{{ $statuses[$order->status] }}</span> @else {{ $statuses[$order->status] }} @endif </p> <h3 class="mb-3">Состав заказа</h3> <table class="table table-bordered"> <tr> <th>№</th> <th>Наименование</th> <th>Цена</th> <th>Кол-во</th> <th>Стоимость</th> </tr> @foreach($order->items as $item) <tr> <td>{{ $loop->iteration }}</td> <td>{{ $item->name }}</td> <td>{{ number_format($item->price, 2, '.', '') }}</td> <td>{{ $item->quantity }}</td> <td>{{ number_format($item->cost, 2, '.', '') }}</td> </tr> @endforeach <tr> <th colspan="4" class="text-right">Итого</th> <th>{{ number_format($order->amount, 2, '.', '') }}</th> </tr> </table> <h3 class="mb-3">Данные покупателя</h3> <p>Имя, фамилия: {{ $order->name }}</p> <p>Адрес почты: <a href="mailto:{{ $order->email }}">{{ $order->email }}</a></p> <p>Номер телефона: {{ $order->phone }}</p> <p>Адрес доставки: {{ $order->address }}</p> @isset ($order->comment) <p>Комментарий: {{ $order->comment }}</p> @endisset @endsection
@extends('layout.admin', ['title' => 'Редактирование заказа']) @section('content') <h1 class="mb-4">Редактирование заказа</h1> <form method="post" action="{{ route('admin.order.update', ['order' => $order->id]) }}"> @csrf @method('PUT') <div class="form-group"> @php $status = old('status') ?? $order->status ?? 0) @endphp <select name="status" class="form-control" title="Статус заказа"> @foreach ($statuses as $key => $value) <option value="{{ $key }}" @if ($key == $status) selected @endif> {{ $value }} </option> @endforeach </select> </div> <div class="form-group"> <input type="text" class="form-control" name="name" placeholder="Имя, Фамилия" required maxlength="255" value="{{ old('name') ?? $order->name ?? '' }}"> </div> <div class="form-group"> <input type="email" class="form-control" name="email" placeholder="Адрес почты" required maxlength="255" value="{{ old('email') ?? $order->email ?? '' }}"> </div> <div class="form-group"> <input type="text" class="form-control" name="phone" placeholder="Номер телефона" required maxlength="255" value="{{ old('phone') ?? $order->phone ?? '' }}"> </div> <div class="form-group"> <input type="text" class="form-control" name="address" placeholder="Адрес доставки" required maxlength="255" value="{{ old('address') ?? $order->address ?? '' }}"> </div> <div class="form-group"> <textarea class="form-control" name="comment" placeholder="Комментарий" maxlength="255" rows="2">{{ old('comment') ?? $order->comment ?? '' }}</textarea> </div> <div class="form-group"> <button type="submit" class="btn btn-success">Сохранить</button> </div> </form> @endsection
- Магазин на Laravel 7, часть 18. Панель управления, пользователи и CRUD страниц сайта
- Магазин на Laravel 7, часть 16. Панель управления, CRUD-операции для товаров каталога
- Магазин на Laravel 7, часть 15. Панель управления, добавление и редактирование брендов
- Магазин на Laravel 7, часть 13. Панель управления, обрезка изображения и валидация данных
- Магазин на Laravel 7, часть 12. Панель управления, создание и редактирование категорий
- Магазин на Laravel 7, часть 24. Фильтр товаров категории по цене, новинкам и лидерам продаж
- Магазин на Laravel 7, часть 23. Главная страница сайта, новинки, лидеры продаж и распродажа
Поиск: Laravel • MySQL • PHP • Web-разработка • База данных • Заказ • Интернет магазин • Каталог товаров • Панель управления • Практика • Фреймворк • Шаблон сайта