22dfb12948
Added height to the OrderPacked event constructor and dispatch call to match the packing form which captures width, length, height, and weight.
242 lines
7.4 KiB
PHP
242 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Order;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class OpsController extends Controller
|
|
{
|
|
/**
|
|
* Display order details via QR token (state-machine driven UI)
|
|
*
|
|
* GET /ops/orders/{token}
|
|
*/
|
|
public function showOrder(Request $request, $token)
|
|
{
|
|
// Find order by QR token
|
|
$order = Order::where('qr_token', $token)->first();
|
|
|
|
if (! $order) {
|
|
Log::warning('QR token not found', ['token' => substr($token, 0, 8) . '...']);
|
|
|
|
return response()->view('ops.order-not-found', [], 404);
|
|
}
|
|
|
|
// Check user authorization - must be logged in ops user
|
|
// TODO: Add middleware to enforce this
|
|
if (! auth()->check() || ! auth()->user()->can('access-ops')) {
|
|
Log::warning('Unauthorized QR access attempt', [
|
|
'order_uuid' => $order->uuid,
|
|
'user_id' => auth()->id(),
|
|
]);
|
|
|
|
return response()->view('ops.unauthorized', [], 403);
|
|
}
|
|
|
|
Log::info('QR order accessed', [
|
|
'order_uuid' => $order->uuid,
|
|
'status' => $order->status,
|
|
'user_id' => auth()->id(),
|
|
]);
|
|
|
|
// Determine available actions based on order status
|
|
$availableActions = $this->getAvailableActions($order);
|
|
|
|
return view('ops.order-detail', [
|
|
'order' => $order,
|
|
'availableActions' => $availableActions,
|
|
'qrUrl' => route('ops.order.show', ['token' => $order->qr_token]),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Confirm packing via QR form
|
|
*
|
|
* POST /ops/orders/{id}/pack
|
|
*/
|
|
public function confirmPacking(Request $request, Order $order)
|
|
{
|
|
// Validate input
|
|
$validated = $request->validate([
|
|
'weight' => ['required', 'numeric', 'min:0.1'],
|
|
'width' => ['required', 'numeric', 'min:1'],
|
|
'length' => ['required', 'numeric', 'min:1'],
|
|
'height' => ['required', 'numeric', 'min:1'],
|
|
]);
|
|
|
|
// Ensure order is in packing state
|
|
if ($order->status !== 'packing' && $order->status !== 'inspection') {
|
|
return response()->json([
|
|
'error' => 'Order cannot be packed in current state',
|
|
'current_status' => $order->status,
|
|
], 409);
|
|
}
|
|
|
|
// Save packing dimensions
|
|
$order->update([
|
|
'packing_width' => $validated['width'],
|
|
'packing_length' => $validated['length'],
|
|
'packing_weight' => $validated['weight'],
|
|
'packing_completed_at' => now(),
|
|
'packed_by' => auth()->id(),
|
|
'status' => 'packing',
|
|
]);
|
|
|
|
Log::info('Order packed via QR', [
|
|
'order_uuid' => $order->uuid,
|
|
'packed_by' => auth()->user()->name,
|
|
'dimensions' => "{$validated['width']}x{$validated['length']}cm, {$validated['weight']}kg",
|
|
]);
|
|
|
|
// Emit event to trigger Slack notification and Trello update
|
|
\App\Events\OrderPacked::dispatch(
|
|
$order,
|
|
(float) $validated['width'],
|
|
(float) $validated['length'],
|
|
(float) $validated['height'],
|
|
(float) $validated['weight'],
|
|
auth()->id()
|
|
);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Order packed successfully',
|
|
'order' => [
|
|
'uuid' => $order->uuid,
|
|
'number' => $order->order_number,
|
|
'status' => $order->status,
|
|
],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Mark inspection passed via QR
|
|
*
|
|
* POST /ops/orders/{id}/inspection-passed
|
|
*/
|
|
public function markInspectionPassed(Request $request, Order $order)
|
|
{
|
|
if ($order->status !== 'inspection') {
|
|
return response()->json([
|
|
'error' => 'Order is not in inspection state',
|
|
'current_status' => $order->status,
|
|
], 409);
|
|
}
|
|
|
|
// Update status
|
|
$order->update(['status' => 'packing']);
|
|
|
|
Log::info('Inspection passed via QR', [
|
|
'order_uuid' => $order->uuid,
|
|
'marked_by' => auth()->user()->name,
|
|
]);
|
|
|
|
// Emit event to trigger Slack/Trello updates
|
|
\App\Events\InspectionPassed::dispatch($order);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Inspection passed',
|
|
'order' => ['uuid' => $order->uuid, 'status' => $order->status],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Flag inspection issue via QR
|
|
*
|
|
* POST /ops/orders/{id}/inspection-failed
|
|
*/
|
|
public function flagInspectionIssue(Request $request, Order $order)
|
|
{
|
|
$validated = $request->validate([
|
|
'issue_description' => ['required', 'string', 'max:500'],
|
|
]);
|
|
|
|
if ($order->status !== 'inspection') {
|
|
return response()->json([
|
|
'error' => 'Order is not in inspection state',
|
|
'current_status' => $order->status,
|
|
], 409);
|
|
}
|
|
|
|
// Update status and store issue
|
|
$order->update([
|
|
'status' => 'review_required',
|
|
'notes' => $validated['issue_description'],
|
|
]);
|
|
|
|
Log::warning('Inspection issue flagged via QR', [
|
|
'order_uuid' => $order->uuid,
|
|
'flagged_by' => auth()->user()->name,
|
|
'issue' => $validated['issue_description'],
|
|
]);
|
|
|
|
// Emit event for listeners to alert ops
|
|
// TODO: Create InspectionFailed event
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Issue flagged - order moved to review',
|
|
'order' => ['uuid' => $order->uuid, 'status' => $order->status],
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Determine which actions are available based on order status
|
|
*/
|
|
private function getAvailableActions(Order $order): array
|
|
{
|
|
return match ($order->status) {
|
|
'inspection' => [
|
|
'markInspectionPassed' => true,
|
|
'flagInspectionIssue' => true,
|
|
],
|
|
'packing' => [
|
|
'confirmPacking' => true,
|
|
],
|
|
'ready_to_ship', 'awaiting_collection', 'in_transit' => [
|
|
'readOnly' => true,
|
|
],
|
|
default => []
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Download QR sticker PDF for printing
|
|
*
|
|
* GET /ops/orders/{id}/sticker/download
|
|
*/
|
|
public function downloadSticker(Order $order)
|
|
{
|
|
// Authorize
|
|
if (! auth()->user()->can('access-ops')) {
|
|
abort(403, 'Unauthorized to access ops interface');
|
|
}
|
|
|
|
$stickerPath = "qr-stickers/{$order->uuid}.pdf";
|
|
|
|
if (! \Illuminate\Support\Facades\Storage::disk('public')->exists($stickerPath)) {
|
|
Log::warning('QR sticker PDF not found', [
|
|
'order_uuid' => $order->uuid,
|
|
'path' => $stickerPath,
|
|
]);
|
|
|
|
abort(404, 'QR sticker not found');
|
|
}
|
|
|
|
Log::info('QR sticker downloaded', [
|
|
'order_uuid' => $order->uuid,
|
|
'user_id' => auth()->id(),
|
|
'user_name' => auth()->user()->name,
|
|
]);
|
|
|
|
return \Illuminate\Support\Facades\Storage::disk('public')->download(
|
|
$stickerPath,
|
|
"QR-{$order->order_number}.pdf"
|
|
);
|
|
}
|
|
}
|