$request->method(), 'content_length' => strlen($request->getContent()), ]); // If empty body or validation ping, just return success if ($request->getContent() === '' || $request->method() === 'HEAD') { return response()->json(['ok' => true], 200); } try { $payload = $request->json()->all(); Log::info('Trello action received', [ 'action' => $payload['action']['type'] ?? 'unknown', 'card' => $payload['action']['data']['card']['name'] ?? 'unknown', ]); match ($payload['action']['type'] ?? null) { 'updateCard' => $this->handleCardUpdate($payload), 'updateCheckItem' => $this->handleChecklistUpdate($payload), default => Log::debug('Unhandled Trello action', [ 'type' => $payload['action']['type'] ?? 'unknown', ]), }; return response()->json(['success' => true], 200); } catch (\Throwable $e) { Log::error('Error processing Trello webhook', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); return response()->json(['success' => true], 200); } } /** * Handle card movement between lists */ private function handleCardUpdate(array $payload): void { $action = $payload['action'] ?? []; $cardData = $action['data']['card'] ?? []; $cardId = $cardData['id'] ?? null; $cardName = $cardData['name'] ?? null; $listAfter = $action['data']['listAfter'] ?? []; $listName = $listAfter['name'] ?? null; if (! $cardId || ! $cardName || ! $listName) { Log::warning('Trello card update missing required data', [ 'card_id' => $cardId, 'card_name' => $cardName, 'list_name' => $listName, ]); return; } // Extract order number from card name (e.g., "Order #1043" or "Order #ORD-20260102-ABC123") if (! preg_match('/Order #([A-Za-z0-9\-]+)/', $cardName, $matches)) { Log::debug('Could not extract order number from card name', ['card_name' => $cardName]); return; } $orderNumber = $matches[1]; // Try to find the order (could be standard or custom) $order = Order::where('order_number', $orderNumber)->first() ?? CustomOrder::where('order_number', $orderNumber)->first(); if (! $order) { Log::warning('Order not found for Trello card', [ 'order_number' => $orderNumber, 'card_name' => $cardName, ]); return; } Log::info('Trello card moved', [ 'order_uuid' => $order->uuid, 'order_number' => $orderNumber, 'list' => $listName, ]); // Handle list-specific actions match ($listName) { 'Ready to Ship' => $this->handleReadyToShipIntent($order, $cardId), 'Awaiting Collection' => $this->handleAwaitingCollectionIntent($order, $cardId), 'In Transit' => Log::info('Card in transit', ['order_uuid' => $order->uuid]), 'Done' => Log::info('Card completed', ['order_uuid' => $order->uuid]), default => Log::debug('Card moved to list', [ 'order_uuid' => $order->uuid, 'list' => $listName, ]), }; } /** * Handle checklist item completion */ private function handleChecklistUpdate(array $payload): void { $action = $payload['action'] ?? []; $cardData = $action['data']['card'] ?? []; $cardName = $cardData['name'] ?? null; $itemName = $action['data']['checkItem']['name'] ?? null; $itemState = $action['data']['checkItem']['state'] ?? null; if ($itemState !== 'complete' || ! $cardName || ! $itemName) { return; } Log::debug('Trello checklist item completed', [ 'card' => $cardName, 'item' => $itemName, ]); // TODO: Map checklist completions to domain events if needed // For now, just log for visibility } /** * Handle "Ready to Ship" intent * * Emit event to trigger shipment creation flow */ private function handleReadyToShipIntent($order, string $cardId): void { Log::info('Ready to Ship intent from Trello', [ 'order_uuid' => $order->uuid, 'card_id' => $cardId, ]); // Emit event so ShippingController can validate and create shipment ReadyToShipIntent::dispatch($order); } /** * Handle "Awaiting Collection" intent * * Verify shipment exists before accepting transition */ private function handleAwaitingCollectionIntent($order, string $cardId): void { Log::info('Awaiting Collection intent from Trello', [ 'order_uuid' => $order->uuid, 'card_id' => $cardId, ]); // Check if order has a waybill (shipment was created) if (! $order->courier_waybill_id) { Log::warning('Cannot move to Awaiting Collection - no shipment created', [ 'order_uuid' => $order->uuid, ]); return; } Log::info('Order ready for collection', [ 'order_uuid' => $order->uuid, 'waybill_id' => $order->courier_waybill_id, ]); } }