Summary of completed work: - Complete buyer portal (browse, cart, checkout, orders, invoices) - Complete seller portal (orders, manifests, fleet, picking) - Business onboarding wizards (buyer 4-step, seller 5-step) - Email verification and registration flows - Notification system for buyers and sellers - Payment term surcharges and pickup/delivery workflows - Filament admin resources (Business, Brand, Product, Order, Invoice, User) - 51 migrations executed successfully Renamed Company -> Business throughout codebase for consistency. Unified authentication flows with password reset. Added audit trail and telescope for debugging. Next: Week 4 Data Migration (Days 22-28) - Product migration (883 products from cannabrands_crm) - Company migration (81 buyer companies) - User migration (preserve password hashes) - Order history migration
172 lines
5.6 KiB
PHP
172 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Buyer;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Order;
|
|
use Illuminate\Http\Request;
|
|
|
|
class OrderController extends Controller
|
|
{
|
|
/**
|
|
* Display a listing of the user's orders.
|
|
*/
|
|
public function index()
|
|
{
|
|
$user = auth()->user();
|
|
$userBusinessIds = $user->businesses->pluck('id')->toArray();
|
|
|
|
$orders = Order::with(['items', 'business', 'location'])
|
|
->where(function ($query) use ($user, $userBusinessIds) {
|
|
$query->where('user_id', $user->id)
|
|
->orWhereIn('business_id', $userBusinessIds);
|
|
})
|
|
->latest()
|
|
->get();
|
|
|
|
$stats = [
|
|
'total' => $orders->count(),
|
|
'new' => $orders->where('status', 'new')->count(),
|
|
'accepted' => $orders->where('status', 'accepted')->count(),
|
|
'in_progress' => $orders->where('status', 'in_progress')->count(),
|
|
'delivered' => $orders->where('status', 'delivered')->count(),
|
|
];
|
|
|
|
return view('buyer.orders.index', compact('orders', 'stats'));
|
|
}
|
|
|
|
/**
|
|
* Display the specified order.
|
|
*/
|
|
public function show(Order $order)
|
|
{
|
|
// Authorization check
|
|
if (!$this->canAccessOrder($order)) {
|
|
abort(403, 'Unauthorized to view this order.');
|
|
}
|
|
|
|
$order->load(['items.product', 'business', 'location', 'user', 'invoice', 'manifest']);
|
|
|
|
return view('buyer.orders.show', compact('order'));
|
|
}
|
|
|
|
/**
|
|
* Accept an order.
|
|
*/
|
|
public function accept(Order $order)
|
|
{
|
|
if (!$this->canAccessOrder($order)) {
|
|
abort(403, 'Unauthorized to modify this order.');
|
|
}
|
|
|
|
if ($order->status !== 'new') {
|
|
return back()->with('error', 'Only new orders can be accepted.');
|
|
}
|
|
|
|
$order->accept();
|
|
|
|
return back()->with('success', "Order {$order->order_number} has been accepted.");
|
|
}
|
|
|
|
/**
|
|
* Cancel an order (buyer-initiated).
|
|
*/
|
|
public function cancel(Order $order, Request $request)
|
|
{
|
|
if (!$this->canAccessOrder($order)) {
|
|
abort(403, 'Unauthorized to modify this order.');
|
|
}
|
|
|
|
if (!$order->canBeCancelledByBuyer()) {
|
|
return back()->with('error', 'This order cannot be cancelled at this stage.');
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'reason' => 'required|string|max:1000',
|
|
]);
|
|
|
|
$order->cancelByBuyer($validated['reason']);
|
|
|
|
return back()->with('success', "Order {$order->order_number} has been cancelled.");
|
|
}
|
|
|
|
/**
|
|
* Update order fulfillment method and related information.
|
|
*/
|
|
public function updateFulfillment(Order $order, Request $request)
|
|
{
|
|
if (!$this->canAccessOrder($order)) {
|
|
abort(403, 'Unauthorized to modify this order.');
|
|
}
|
|
|
|
if (!$order->canChangeFulfillmentMethod()) {
|
|
return back()->with('error', 'Cannot change fulfillment method after manifest has been created.');
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'delivery_method' => 'required|in:delivery,pickup',
|
|
'location_id' => 'nullable|exists:businesses,id',
|
|
'pickup_driver_first_name' => 'nullable|string|max:255',
|
|
'pickup_driver_last_name' => 'nullable|string|max:255',
|
|
'pickup_driver_license' => 'nullable|string|max:255',
|
|
'pickup_driver_phone' => 'nullable|string|max:15',
|
|
'pickup_vehicle_plate' => 'nullable|string|max:255',
|
|
]);
|
|
|
|
// Update order with validated data
|
|
$order->update([
|
|
'delivery_method' => $validated['delivery_method'],
|
|
'location_id' => $validated['location_id'] ?? $order->location_id,
|
|
'pickup_driver_first_name' => $validated['pickup_driver_first_name'] ?? null,
|
|
'pickup_driver_last_name' => $validated['pickup_driver_last_name'] ?? null,
|
|
'pickup_driver_license' => $validated['pickup_driver_license'] ?? null,
|
|
'pickup_driver_phone' => $validated['pickup_driver_phone'] ?? null,
|
|
'pickup_vehicle_plate' => $validated['pickup_vehicle_plate'] ?? null,
|
|
]);
|
|
|
|
$method = $order->isPickup() ? 'pickup' : 'delivery';
|
|
return back()->with('success', "Fulfillment method updated to {$method}.");
|
|
}
|
|
|
|
/**
|
|
* Download manifest PDF for an order.
|
|
*/
|
|
public function downloadManifestPdf(Order $order)
|
|
{
|
|
if (!$this->canAccessOrder($order)) {
|
|
abort(403, 'Unauthorized to access this order.');
|
|
}
|
|
|
|
$manifest = $order->manifest;
|
|
|
|
if (!$manifest) {
|
|
return back()->with('error', 'No manifest found for this order.');
|
|
}
|
|
|
|
if (!$manifest->pdf_path || !\Storage::disk('local')->exists($manifest->pdf_path)) {
|
|
return back()->with('error', 'Manifest PDF not found.');
|
|
}
|
|
|
|
return \Storage::disk('local')->response(
|
|
$manifest->pdf_path,
|
|
"manifest-{$manifest->manifest_number}.pdf",
|
|
[
|
|
'Content-Type' => 'application/pdf',
|
|
'Content-Disposition' => 'inline; filename="manifest-' . $manifest->manifest_number . '.pdf"'
|
|
]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if current user can access the order.
|
|
*/
|
|
protected function canAccessOrder(Order $order): bool
|
|
{
|
|
$user = auth()->user();
|
|
$userBusinessIds = $user->businesses->pluck('id')->toArray();
|
|
|
|
return $order->user_id === $user->id ||
|
|
in_array($order->business_id, $userBusinessIds);
|
|
}
|
|
}
|