We are still working on orders. It is time to implement API endpoints to manage orders for staff users.
Create API resources for Order Model with the staff scope. We want to separate these resources to avoid affecting responses when customers request their orders.
php artisan make:resource Api/V1/Staff/OrderResource
php artisan make:resource Api/V1/Staff/OrderCollection
Create a new API controller for staff members.
php artisan make:controller Api/V1/Staff/OrderController
Unlike app/Http/Controllers/Staff/OrderController.php
, returning current and past orders in a single method, we separate them in the API. Applications consuming API won't have to group or filter them manually.
app/Http/Controllers/Api/V1/Staff/OrderController.php
namespace App\Http\Controllers\Api\V1\Staff; use App\Http\Controllers\Controller;use App\Http\Requests\Staff\UpdateOrderRequest;use App\Http\Resources\Api\V1\Staff\OrderCollection;use App\Http\Resources\Api\V1\Staff\OrderResource;use App\Models\Order;use Illuminate\Http\JsonResponse;use Illuminate\Http\Response; class OrderController extends Controller{ public function index(): OrderCollection { $orders = Order::current() ->with(['customer', 'products']) ->where('restaurant_id', auth()->user()->restaurant_id) ->latest() ->get(); return new OrderCollection($orders); } public function past(): OrderCollection { $orders = Order::past() ->with(['customer', 'products']) ->where('restaurant_id', auth()->user()->restaurant_id) ->latest() ->get(); return new OrderCollection($orders); } public function update(UpdateOrderRequest $request, $orderId): JsonResponse { $order = Order::where('restaurant_id', $request->user()->restaurant_id) ->findOrFail($orderId); $order->update($request->validated()); return (new OrderResource($order)) ->response() ->setStatusCode(Response::HTTP_ACCEPTED); }}
Then create a new API routes file for staff members.
routes/api/v1/staff.php
use App\Http\Controllers\Api\V1\Staff\OrderController;use Illuminate\Support\Facades\Route; Route::group([ 'prefix' => 'staff', 'as' => 'staff.', 'middleware' => ['auth'],], function () { Route::get('orders/past', [OrderController::class, 'past'])->name('orders.past'); Route::apiResource('orders', OrderController::class);});
And include it in the main api.php
file.
routes/api.php
include __DIR__ . '/api/v1/admin.php';include __DIR__ . '/api/v1/vendor.php';include __DIR__ . '/api/v1/customer.php';include __DIR__ . '/api/v1/staff.php';
We already have OrderService
present. We can add more methods to retrieve restaurant orders.
app/Services/OrderService.php
public function getOrders(?string $period = null): Collection{ $query = Order::query()->with(['customer', 'products']) ->where('restaurant_id', auth()->user()->restaurant_id); match ($period) { 'current' => $query->current()->latest(), 'past' => $query->past()->latest('updated_at'), default => $query->latest(), }; return $query->get();} public function getCurrentOrders(): Collection{ return $this->getOrders('current');} public function getPastOrders(): Collection{ return $this->getOrders('past');} public function updateOrder(Order $order, array $attributes): Order{ $order->update($attributes); return $order;}
We retrieve all restaurant orders in a single getOrders
method to avoid repeating ourselves in the code. It accepts the period
as an argument and applies scopes depending on that argument. That allows us to create helper methods such as getCurrentOrders
and getPastOrders
without manually passing an argument each time.
Now we can update staff API OrderController
to consume service.
app/Http/Controllers/Api/V1/Staff/OrderController.php
use App\Http\Resources\Api\V1\Staff\OrderCollection;use App\Http\Resources\Api\V1\Staff\OrderResource;use App\Models\Order;use App\Services\OrderService; use Illuminate\Http\JsonResponse;use Illuminate\Http\Response; class OrderController extends Controller{ public function __construct(public OrderService $orderService) { } public function index(): OrderCollection { $orders = Order::current() ->with(['customer', 'products']) ->where('restaurant_id', auth()->user()->restaurant_id) ->latest() ->get(); return new OrderCollection($orders); return new OrderCollection($this->orderService->getCurrentOrders()); } public function past(): OrderCollection { $orders = Order::past() ->with(['customer', 'products']) ->where('restaurant_id', auth()->user()->restaurant_id) ->latest() ->get(); return new OrderCollection($orders); return new OrderCollection($this->orderService->getPastOrders()); } public function update(UpdateOrderRequest $request, $orderId): JsonResponse { $order = Order::where('restaurant_id', $request->user()->restaurant_id) ->findOrFail($orderId); $order->update($request->validated()); $order = $this->orderService->updateOrder($order, $request->validated()); return (new OrderResource($order)) ->response() ->setStatusCode(Response::HTTP_ACCEPTED); }}
In the same way, we update the Web OrderController
controller.
app/Http/Controllers/Staff/OrderController.php
use App\Http\Controllers\Controller;use App\Http\Requests\Staff\UpdateOrderRequest;use App\Models\Order; use App\Services\OrderService;use Inertia\Inertia;use Inertia\Response; class OrderController extends Controller{ public function __construct(public OrderService $orderService) { } public function index(): Response { $currentOrders = Order::current() ->with(['customer', 'products']) ->where('restaurant_id', auth()->user()->restaurant_id) ->latest() ->get(); $pastOrders = Order::past() ->with(['customer', 'products']) ->where('restaurant_id', auth()->user()->restaurant_id) ->latest() ->get(); return Inertia::render('Staff/Orders', [ 'current_orders' => $currentOrders, 'past_orders' => $pastOrders, 'current_orders' => $this->orderService->getCurrentOrders(), 'past_orders' => $this->orderService->getPastOrders(), 'order_status' => OrderStatus::toArray(), ]); } public function update(UpdateOrderRequest $request, $orderId) { $order = Order::where('restaurant_id', $request->user()->restaurant_id) ->findOrFail($orderId); $order->update($request->validated()); $this->orderService->updateOrder($order, $request->validated()); return back(); }}
We didn't add the orders
relationship to the Restaurant
Model yet. Let's do this now.
app/Models/Restaurant.php
public function orders(): HasMany{ return $this->hasMany(Order::class);}
Add more tests to API OrderTest
.
tests/Feature/Api/OrderTest.php
public function test_staff_members_can_see_orders(): void{ $staff = User::factory()->staff()->create(); $response = $this ->actingAs($staff) ->get(route('api.staff.orders.index')); $response->assertOk();} public function test_staff_members_can_past_orders(): void{ $staff = User::factory()->staff()->create(); $response = $this ->actingAs($staff) ->get(route('api.staff.orders.past')); $response->assertOk();} public function test_staff_can_update_order(): void{ $customer = User::factory()->customer()->create(); $restaurant = Restaurant::first(); $product = $restaurant->categories()->first() ->products()->first(); $this->actingAs($customer)->postJson(route('api.customer.cart.add', $product)); $this->actingAs($customer)->postJson(route('api.customer.orders.store')); $staff = $restaurant->staff()->first(); $order = $restaurant->orders()->first(); $request = $this->actingAs($staff)->putJson(route('api.staff.orders.update', $order), [ 'status' => OrderStatus::PREPARING->value, ]); $request->assertAccepted();} public function test_staff_cant_update_order_with_invalid_status(): void{ $customer = User::factory()->customer()->create(); $restaurant = Restaurant::first(); $product = $restaurant->categories()->first() ->products()->first(); $this->actingAs($customer)->postJson(route('api.customer.cart.add', $product)); $this->actingAs($customer)->postJson(route('api.customer.orders.store')); $staff = $restaurant->staff()->first(); $order = $restaurant->orders()->first(); $request = $this->actingAs($staff)->putJson(route('api.staff.orders.update', $order), [ 'status' => 'invalid_random_status', ]); $request->assertUnprocessable();}
Add more tests to Web OrderTest
.
tests/Feature/Web/OrderTest.php
public function test_staff_members_can_see_orders(): void{ $staff = User::factory()->staff()->create(); $response = $this ->actingAs($staff) ->get(route('staff.orders.index')); $response->assertInertia(function (AssertableInertia $page) { return $page->component('Staff/Orders') ->has('current_orders') ->has('past_orders') ->has('order_status'); });} public function test_staff_can_update_order(): void{ $customer = User::factory()->customer()->create(); $restaurant = Restaurant::first(); $product = $restaurant->categories()->first() ->products()->first(); $this->actingAs($customer)->post(route('customer.cart.add', $product)); $this->actingAs($customer)->post(route('customer.orders.store')); $staff = $restaurant->staff()->first(); $order = $restaurant->orders()->first(); $request = $this->actingAs($staff)->put(route('staff.orders.update', $order), [ 'status' => OrderStatus::PREPARING->value, ]); $request->assertRedirect()->assertSessionDoesntHaveErrors();} public function test_staff_cant_update_order_with_invalid_status(): void{ $customer = User::factory()->customer()->create(); $restaurant = Restaurant::first(); $product = $restaurant->categories()->first() ->products()->first(); $this->actingAs($customer)->post(route('customer.cart.add', $product)); $this->actingAs($customer)->post(route('customer.orders.store')); $staff = $restaurant->staff()->first(); $order = $restaurant->orders()->first(); $request = $this->actingAs($staff)->put(route('staff.orders.update', $order), [ 'status' => 'invalid_random_status', ]); $request->assertRedirect()->assertSessionHasErrors(['status']);}
Finally, we can run all OrderTest
cases.
php artisan test --filter OrderTest
PASS Tests\Feature\Api\OrderTest✓ customer can place order 1.18s✓ customer cant place empty order 0.24s✓ customer can see orders 0.26s✓ staff members can see orders 0.21s✓ staff members can past orders 0.31s✓ staff can update order 0.31s✓ staff cant update order with invalid status 0.25s PASS Tests\Feature\Web\OrderTest✓ customer can place order 0.26s✓ customer cant place empty order 0.25s✓ customer can see orders 0.25s✓ staff members can see orders 0.22s✓ staff can update order 0.25s✓ staff cant update order with invalid status 0.27s Tests: 13 passed (36 assertions)Duration: 4.33s