Skip to content

Commit c4398ff

Browse files
committed
feat: Add admin orders management system
- Create orders and order_items database tables with migrations - Implement Order and OrderItem models with relationships - Add OrderRepository and OrderService with filtering capabilities - Create OrderController with index, show, and updateStatus endpoints - Build Order Index page with card layout and filtering - Create OrderStatusModal component with sequential status updates - Implement Order Show page with detailed order information - Add useFormatters composable for currency and date formatting - Update admin routes and TypeScript interfaces for orders
1 parent d43423f commit c4398ff

18 files changed

Lines changed: 1074 additions & 10 deletions

File tree

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Admin;
4+
5+
use App\Http\Controllers\Controller;
6+
use App\Http\Requests\Order\UpdateOrderStatusRequest;
7+
use App\Services\OrderService;
8+
use Illuminate\Http\Request;
9+
use Inertia\Inertia;
10+
11+
class OrderController extends Controller
12+
{
13+
/**
14+
* Inject Order Service
15+
*
16+
* @param OrderService $orderService
17+
*/
18+
public function __construct(
19+
protected OrderService $orderService
20+
) {}
21+
22+
/**
23+
* Display a listing of the resource.
24+
*/
25+
public function index(Request $request)
26+
{
27+
$filters = $request->only([
28+
'search',
29+
'status',
30+
'payment_method',
31+
'delivery_method'
32+
]);
33+
34+
$orders = $this->orderService->getAllPaginated(10, $filters);
35+
36+
return Inertia::render('admin/Order/Index', [
37+
'orders' => $orders,
38+
'filters' => $filters,
39+
]);
40+
}
41+
42+
/**
43+
* Display the specified resource.
44+
*/
45+
public function show(int $id)
46+
{
47+
$order = $this->orderService->find($id);
48+
49+
if (! $order) {
50+
return $this->flashError('Order not found', 'admin.orders.index');
51+
}
52+
53+
return Inertia::render('admin/Order/Show', [
54+
'order' => $order,
55+
]);
56+
}
57+
58+
/**
59+
* Update order status.
60+
*/
61+
public function updateStatus(UpdateOrderStatusRequest $request, int $id)
62+
{
63+
$status = $request->validated()['status'];
64+
$updated = $this->orderService->updateStatus($id, $status);
65+
66+
if ($updated) {
67+
return $this->flashSuccess('Order status updated successfully', 'admin.orders.index');
68+
}
69+
70+
return $this->flashError('Order not found', 'admin.orders.index');
71+
}
72+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
namespace App\Http\Requests\Order;
4+
5+
use App\Enums\UserRole;
6+
use Illuminate\Foundation\Http\FormRequest;
7+
use Illuminate\Support\Facades\Auth;
8+
9+
class UpdateOrderStatusRequest extends FormRequest
10+
{
11+
/**
12+
* Determine if the user is authorized to make this request.
13+
*/
14+
public function authorize(): bool
15+
{
16+
return Auth::check() && Auth::user()->role === UserRole::Admin;
17+
}
18+
19+
/**
20+
* Get the validation rules that apply to the request.
21+
*
22+
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
23+
*/
24+
public function rules(): array
25+
{
26+
return [
27+
'status' => 'required|string|in:pending,confirmed,assembled,shipped,delivered,paid,cancelled',
28+
];
29+
}
30+
31+
/**
32+
* Get custom validation messages
33+
*
34+
* @return array<string, string>
35+
*/
36+
public function messages(): array
37+
{
38+
return [
39+
'status.required' => 'Please select a status.',
40+
'status.in' => 'Please select a valid status from the list.',
41+
];
42+
}
43+
}

app/Models/Item.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,14 @@ public function damagedItems(): HasMany
4242
{
4343
return $this->hasMany(DamagedItem::class, 'item_id');
4444
}
45+
46+
/**
47+
* Get all of the order items for the Item
48+
*
49+
* @return \Illuminate\Database\Eloquent\Relations\HasMany
50+
*/
51+
public function orderItems(): HasMany
52+
{
53+
return $this->hasMany(OrderItem::class, 'item_id');
54+
}
4555
}

app/Models/Order.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
7+
use Illuminate\Database\Eloquent\Relations\HasMany;
8+
9+
class Order extends Model
10+
{
11+
protected $fillable = [
12+
'user_id',
13+
'status',
14+
'payment_method',
15+
'total_amount',
16+
'delivery_method',
17+
'delivery_address',
18+
];
19+
20+
/**
21+
* Get the user that owns the Order
22+
*
23+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
24+
*/
25+
public function user(): BelongsTo
26+
{
27+
return $this->belongsTo(User::class, 'user_id');
28+
}
29+
30+
/**
31+
* Get all of the order items for the Order
32+
*
33+
* @return \Illuminate\Database\Eloquent\Relations\HasMany
34+
*/
35+
public function orderItems(): HasMany
36+
{
37+
return $this->hasMany(OrderItem::class, 'order_id');
38+
}
39+
}

app/Models/OrderItem.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
7+
8+
class OrderItem extends Model
9+
{
10+
protected $fillable = [
11+
'order_id',
12+
'item_id',
13+
'quantity',
14+
'unit_price',
15+
];
16+
17+
protected $casts = [
18+
'unit_price' => 'decimal:2',
19+
];
20+
21+
/**
22+
* Get the order that owns the OrderItem
23+
*
24+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
25+
*/
26+
public function order(): BelongsTo
27+
{
28+
return $this->belongsTo(Order::class, 'order_id');
29+
}
30+
31+
/**
32+
* Get the item that owns the OrderItem
33+
*
34+
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
35+
*/
36+
public function item(): BelongsTo
37+
{
38+
return $this->belongsTo(Item::class, 'item_id');
39+
}
40+
}

app/Providers/AppServiceProvider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
use App\Repositories\Interfaces\BaseRepositoryInterface;
88
use App\Repositories\Interfaces\DamagedItemRepositoryInterface;
99
use App\Repositories\Interfaces\ItemRepositoryInterface;
10+
use App\Repositories\Interfaces\OrderRepositoryInterface;
1011
use App\Repositories\Interfaces\SupplierRepositoryInterface;
1112
use App\Repositories\ItemRepository;
13+
use App\Repositories\OrderRepository;
1214
use App\Repositories\SupplierRepository;
1315
use Illuminate\Support\ServiceProvider;
1416

@@ -24,6 +26,7 @@ class AppServiceProvider extends ServiceProvider
2426
ItemRepositoryInterface::class => ItemRepository::class,
2527
SupplierRepositoryInterface::class => SupplierRepository::class,
2628
DamagedItemRepositoryInterface::class => DamagedItemRepository::class,
29+
OrderRepositoryInterface::class => OrderRepository::class
2730
];
2831

2932
/**
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace App\Repositories\Interfaces;
4+
5+
interface OrderRepositoryInterface extends BaseRepositoryInterface
6+
{
7+
//
8+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Repositories;
4+
5+
use App\Models\Order;
6+
use App\Repositories\Interfaces\OrderRepositoryInterface;
7+
8+
class OrderRepository extends BaseRepository implements OrderRepositoryInterface
9+
{
10+
/**
11+
* Inject the Order model
12+
*
13+
* @param Order $order
14+
*/
15+
public function __construct(Order $order)
16+
{
17+
parent::__construct($order);
18+
}
19+
}

app/Services/OrderService.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
use App\Repositories\Interfaces\OrderRepositoryInterface;
6+
use App\Traits\Filterable;
7+
8+
class OrderService
9+
{
10+
use Filterable;
11+
12+
/**
13+
* Inject the order repository
14+
*
15+
* @param OrderRepositoryInterface $orderRepo
16+
*/
17+
public function __construct(
18+
protected OrderRepositoryInterface $orderRepo
19+
) {}
20+
21+
/**
22+
* Get paginated orders with filters
23+
*
24+
* @param int $perPage
25+
* @param array $filters
26+
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
27+
*/
28+
public function getAllPaginated(int $perPage = 10, array $filters = [])
29+
{
30+
$query = $this->orderRepo->query()->with(['user', 'orderItems.item']);
31+
32+
if (! empty($filters['search'])) {
33+
$query->where(function ($q) use ($filters) {
34+
$q->where('id', 'like', '%' . $filters['search'] . '%')
35+
->orWhereHas('user', function ($userQuery) use ($filters) {
36+
$userQuery->where('name', 'like', '%' . $filters['search'] . '%');
37+
});
38+
});
39+
}
40+
41+
$this->applyExactFilter($query, 'status', $filters['status'] ?? null);
42+
$this->applyExactFilter($query, 'payment_method', $filters['payment_method'] ?? null);
43+
$this->applyExactFilter($query, 'delivery_method', $filters['delivery_method'] ?? null);
44+
45+
return $query->orderBy('created_at', 'desc')->paginate($perPage);
46+
}
47+
48+
/**
49+
* Find an order by ID
50+
*
51+
* @param int $id
52+
* @return Order|null
53+
*/
54+
public function find(int $id)
55+
{
56+
return $this->orderRepo->query()
57+
->with(['user', 'orderItems.item'])
58+
->find($id);
59+
}
60+
61+
/**
62+
* Update order status
63+
*
64+
* @param int $id
65+
* @param string $status
66+
* @return bool
67+
*/
68+
public function updateStatus(int $id, string $status): bool
69+
{
70+
$data = ['status' => $status];
71+
72+
return $this->orderRepo->update($id, $data);
73+
}
74+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::create('orders', function (Blueprint $table) {
15+
$table->id();
16+
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
17+
$table->enum('status', [
18+
'pending',
19+
'confirmed',
20+
'assembled',
21+
'shipped',
22+
'delivered',
23+
'paid',
24+
'cancelled',
25+
])->default('pending');
26+
$table->enum('payment_method', ['cash', 'gcash', 'bank_transfer'])->default('cash');
27+
$table->decimal('total_amount', 10, 2);
28+
$table->enum('delivery_method', ['walk_in', 'delivery'])->default('walk_in');
29+
$table->text('delivery_address')->nullable();
30+
$table->timestamps();
31+
});
32+
}
33+
34+
/**
35+
* Reverse the migrations.
36+
*/
37+
public function down(): void
38+
{
39+
Schema::dropIfExists('orders');
40+
}
41+
};

0 commit comments

Comments
 (0)