abf5ced6d7
- Show inspection pass/fail buttons when order status is 'printing' in ops page - Updated getAvailableActions() to include 'printing' in inspection actions - Changed markInspectionPassed() to accept both 'inspection' and 'printing' statuses - Changed markInspectionPassed() to move order to 'awaiting_collection' instead of 'packing' - Updated flagInspectionIssue() to accept both 'inspection' and 'printing' statuses - Updated order-detail.blade.php conditional to show inspection actions for both 'inspection' and 'printing' statuses
357 lines
12 KiB
PHP
357 lines
12 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_height' => $validated['height'],
|
|
'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 (! in_array($order->status, ['inspection', 'printing'])) {
|
|
return response()->json([
|
|
'error' => 'Order is not in inspection or printing state',
|
|
'current_status' => $order->status,
|
|
], 409);
|
|
}
|
|
|
|
// Update status to awaiting_collection (move to inspected list in Trello)
|
|
$order->update(['status' => 'awaiting_collection']);
|
|
|
|
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 (! in_array($order->status, ['inspection', 'printing'])) {
|
|
return response()->json([
|
|
'error' => 'Order is not in inspection or printing 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
|
|
\App\Events\InspectionFailed::dispatch($order, $validated['issue_description']);
|
|
|
|
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', 'printing' => [
|
|
'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"
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Re-download shipment PDFs from Shiplogic API
|
|
*
|
|
* POST /ops/orders/{id}/redownload-pdfs
|
|
*/
|
|
public function redownloadShipmentPdfs(Order $order)
|
|
{
|
|
// Authorize
|
|
if (! auth()->user()->can('access-ops')) {
|
|
abort(403, 'Unauthorized to access ops interface');
|
|
}
|
|
|
|
// Only allow for orders with shipments
|
|
if (! $order->courier_shipment_id) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'No shipment exists for this order',
|
|
], 422);
|
|
}
|
|
|
|
try {
|
|
$courierService = new \App\Services\CourierService();
|
|
$result = $courierService->redownloadShipmentPdfs($order);
|
|
|
|
Log::info('Shipment PDFs re-downloaded via ops', [
|
|
'order_uuid' => $order->uuid,
|
|
'order_number' => $order->order_number,
|
|
'user_id' => auth()->id(),
|
|
'user_name' => auth()->user()->name,
|
|
'success' => $result['success'],
|
|
]);
|
|
|
|
if ($result['success']) {
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => $result['message'],
|
|
'sticker_path' => $result['sticker_path'],
|
|
'waybill_path' => $result['waybill_path'],
|
|
]);
|
|
} else {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => $result['message'],
|
|
], 500);
|
|
}
|
|
} catch (\Exception $e) {
|
|
Log::error('Failed to re-download shipment PDFs via ops', [
|
|
'order_uuid' => $order->uuid,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to re-download PDFs: ' . $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark order as ready for collection and move Trello card
|
|
*
|
|
* POST /ops/orders/{id}/ready-for-collection
|
|
*/
|
|
public function markReadyForCollection(Order $order)
|
|
{
|
|
// Authorize
|
|
if (! auth()->user()->can('access-ops')) {
|
|
abort(403, 'Unauthorized to access ops interface');
|
|
}
|
|
|
|
// Only allow for orders with shipments in ready_to_ship status
|
|
if (! $order->courier_shipment_id || $order->status !== 'ready_to_ship') {
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Order must have a shipment and be in Ready to Ship status',
|
|
], 422);
|
|
}
|
|
|
|
try {
|
|
// Update order status
|
|
$order->update([
|
|
'status' => 'awaiting_collection',
|
|
'courier_status' => 'awaiting_collection',
|
|
]);
|
|
|
|
// Move Trello card to Awaiting Collection
|
|
if ($order->trello_card_id) {
|
|
$trelloService = new \App\Services\TrelloService();
|
|
$trelloService->moveCard($order->trello_card_id, 'Awaiting Collection');
|
|
}
|
|
|
|
Log::info('Order marked ready for collection via ops', [
|
|
'order_uuid' => $order->uuid,
|
|
'order_number' => $order->order_number,
|
|
'user_id' => auth()->id(),
|
|
'user_name' => auth()->user()->name,
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Order moved to Awaiting Collection',
|
|
]);
|
|
} catch (\Exception $e) {
|
|
Log::error('Failed to mark order ready for collection', [
|
|
'order_uuid' => $order->uuid,
|
|
'error' => $e->getMessage(),
|
|
]);
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => 'Failed to mark ready for collection: ' . $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
} |