Skip to content

Commit d835ca3

Browse files
committed
feat: Add order management API routes and implement order history in frontend
- Added order API routes for users and admin in `api.php` - Implemented `AdminOrderController` for admin order management - Created `OrderController` for user order management - Developed `OrderConfirmation` mail class for order confirmation emails - Designed `confirmation.blade.php` for order confirmation email template - Built `OrderHistory` component in frontend to display user orders - Enhanced `useOrders` hook to manage order fetching and cancellation
1 parent 8fc3fe1 commit d835ca3

File tree

8 files changed

+1136
-0
lines changed

8 files changed

+1136
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api;
4+
5+
use App\Http\Controllers\Controller;
6+
use App\Models\Order;
7+
use Illuminate\Http\Request;
8+
use Illuminate\Http\JsonResponse;
9+
10+
class AdminOrderController extends Controller
11+
{
12+
/**
13+
* Display a listing of all orders (admin only).
14+
*/
15+
public function index(Request $request): JsonResponse
16+
{
17+
try {
18+
$query = Order::with(['user', 'items.productVariant.product', 'payments', 'shipments']);
19+
20+
// Filter by user
21+
if ($request->has('user_id')) {
22+
$query->where('user_id', $request->user_id);
23+
}
24+
25+
// Filter by status
26+
if ($request->has('status')) {
27+
$query->where('status', $request->status);
28+
}
29+
30+
// Filter by payment status
31+
if ($request->has('payment_status')) {
32+
$query->where('payment_status', $request->payment_status);
33+
}
34+
35+
// Filter by shipping status
36+
if ($request->has('shipping_status')) {
37+
$query->where('shipping_status', $request->shipping_status);
38+
}
39+
40+
// Filter by date range
41+
if ($request->has('date_from')) {
42+
$query->where('created_at', '>=', $request->date_from);
43+
}
44+
45+
if ($request->has('date_to')) {
46+
$query->where('created_at', '<=', $request->date_to);
47+
}
48+
49+
// Search by order number
50+
if ($request->has('search')) {
51+
$search = $request->search;
52+
$query->where('order_number', 'like', "%{$search}%");
53+
}
54+
55+
// Sorting
56+
$sortBy = $request->get('sort_by', 'created_at');
57+
$sortDirection = $request->get('sort_direction', 'desc');
58+
59+
switch ($sortBy) {
60+
case 'total_amount':
61+
$query->orderBy('total_amount', $sortDirection);
62+
break;
63+
case 'status':
64+
$query->orderBy('status', $sortDirection);
65+
break;
66+
case 'user_id':
67+
$query->orderBy('user_id', $sortDirection);
68+
break;
69+
case 'created_at':
70+
default:
71+
$query->orderBy('created_at', $sortDirection);
72+
break;
73+
}
74+
75+
$perPage = min($request->get('per_page', 15), 100);
76+
$orders = $query->paginate($perPage);
77+
78+
return response()->json([
79+
'success' => true,
80+
'data' => $orders,
81+
'message' => 'Orders retrieved successfully'
82+
]);
83+
84+
} catch (\Exception $e) {
85+
return response()->json([
86+
'success' => false,
87+
'message' => 'Failed to retrieve orders',
88+
'error' => $e->getMessage()
89+
], 500);
90+
}
91+
}
92+
93+
/**
94+
* Display the specified order (admin only).
95+
*/
96+
public function show(Order $order): JsonResponse
97+
{
98+
try {
99+
$order->load(['user', 'items.productVariant.product', 'payments', 'shipments', 'billingAddress', 'shippingAddress']);
100+
101+
return response()->json([
102+
'success' => true,
103+
'data' => $order,
104+
'message' => 'Order retrieved successfully'
105+
]);
106+
107+
} catch (\Exception $e) {
108+
return response()->json([
109+
'success' => false,
110+
'message' => 'Failed to retrieve order',
111+
'error' => $e->getMessage()
112+
], 500);
113+
}
114+
}
115+
116+
/**
117+
* Update order status (admin only).
118+
*/
119+
public function updateStatus(Request $request, Order $order): JsonResponse
120+
{
121+
try {
122+
$validated = $request->validate([
123+
'status' => 'required|in:pending,paid,processing,shipped,delivered,cancelled',
124+
'notify_customer' => 'boolean',
125+
]);
126+
127+
$oldStatus = $order->status;
128+
$order->status = $validated['status'];
129+
$order->save();
130+
131+
// Update related statuses based on new status
132+
switch ($validated['status']) {
133+
case 'paid':
134+
$order->markAsPaid();
135+
break;
136+
case 'shipped':
137+
$order->markAsShipped();
138+
break;
139+
case 'delivered':
140+
$order->markAsDelivered();
141+
break;
142+
case 'cancelled':
143+
$order->cancel();
144+
break;
145+
}
146+
147+
// TODO: Send email notification if notify_customer is true
148+
149+
return response()->json([
150+
'success' => true,
151+
'data' => $order->fresh()->load(['user', 'items.productVariant.product']),
152+
'message' => 'Order status updated successfully',
153+
'previous_status' => $oldStatus,
154+
'new_status' => $order->status
155+
]);
156+
157+
} catch (\Illuminate\Validation\ValidationException $e) {
158+
return response()->json([
159+
'success' => false,
160+
'message' => 'Validation failed',
161+
'errors' => $e->errors()
162+
], 422);
163+
164+
} catch (\Exception $e) {
165+
return response()->json([
166+
'success' => false,
167+
'message' => 'Failed to update order status',
168+
'error' => $e->getMessage()
169+
], 500);
170+
}
171+
}
172+
173+
/**
174+
* Get order statistics (admin only).
175+
*/
176+
public function stats(Request $request): JsonResponse
177+
{
178+
try {
179+
$query = Order::query();
180+
181+
// Filter by date range
182+
if ($request->has('date_from')) {
183+
$query->where('created_at', '>=', $request->date_from);
184+
}
185+
186+
if ($request->has('date_to')) {
187+
$query->where('created_at', '<=', $request->date_to);
188+
}
189+
190+
$stats = [
191+
'total_orders' => (clone $query)->count(),
192+
'total_revenue' => (clone $query)->sum('total_amount'),
193+
'pending_orders' => (clone $query)->where('status', 'pending')->count(),
194+
'paid_orders' => (clone $query)->where('status', 'paid')->count(),
195+
'shipped_orders' => (clone $query)->where('status', 'shipped')->count(),
196+
'delivered_orders' => (clone $query)->where('status', 'delivered')->count(),
197+
'cancelled_orders' => (clone $query)->where('status', 'cancelled')->count(),
198+
'average_order_value' => (clone $query)->where('status', 'delivered')->avg('total_amount'),
199+
];
200+
201+
return response()->json([
202+
'success' => true,
203+
'data' => $stats,
204+
'message' => 'Order statistics retrieved successfully'
205+
]);
206+
207+
} catch (\Exception $e) {
208+
return response()->json([
209+
'success' => false,
210+
'message' => 'Failed to retrieve order statistics',
211+
'error' => $e->getMessage()
212+
], 500);
213+
}
214+
}
215+
216+
/**
217+
* Process refund for an order (admin only).
218+
*/
219+
public function refund(Request $request, Order $order): JsonResponse
220+
{
221+
try {
222+
$validated = $request->validate([
223+
'amount' => 'nullable|numeric|min:0',
224+
'reason' => 'required|string|max:255',
225+
]);
226+
227+
$user = auth()->user();
228+
229+
// Check if order can be refunded
230+
if (!in_array($order->status, ['paid', 'shipped', 'delivered'])) {
231+
return response()->json([
232+
'success' => false,
233+
'message' => 'Order cannot be refunded in its current status'
234+
], 422);
235+
}
236+
237+
// Get the payment
238+
$payment = $order->payments()->where('status', 'succeeded')->first();
239+
if (!$payment) {
240+
return response()->json([
241+
'success' => false,
242+
'message' => 'No successful payment found for this order'
243+
], 404);
244+
}
245+
246+
// TODO: Process refund via Stripe
247+
// For now, we'll just update the order status
248+
249+
return response()->json([
250+
'success' => true,
251+
'data' => $order->fresh(),
252+
'message' => 'Refund processed successfully'
253+
]);
254+
255+
} catch (\Illuminate\Validation\ValidationException $e) {
256+
return response()->json([
257+
'success' => false,
258+
'message' => 'Validation failed',
259+
'errors' => $e->errors()
260+
], 422);
261+
262+
} catch (\Exception $e) {
263+
return response()->json([
264+
'success' => false,
265+
'message' => 'Failed to process refund',
266+
'error' => $e->getMessage()
267+
], 500);
268+
}
269+
}
270+
}

0 commit comments

Comments
 (0)