diff --git a/app/Listeners/NotifySlackOnOrderCreated.php b/app/Listeners/NotifySlackOnOrderCreated.php index d8eb4a0..a1c7f33 100644 --- a/app/Listeners/NotifySlackOnOrderCreated.php +++ b/app/Listeners/NotifySlackOnOrderCreated.php @@ -34,6 +34,27 @@ class NotifySlackOnOrderCreated if ($cardId) { $order->update(['trello_card_id' => $cardId]); + + // Extract design name from first order item's product + $designName = $order->items->first()?->product?->name ?? '—'; + + // Calculate print size from first item's dimensions + $printSize = '—'; + $firstItem = $order->items->first(); + if ($firstItem) { + if ($firstItem->type === 'wallpaper' && $firstItem->length) { + $printSize = $firstItem->length . 'm'; + } elseif ($firstItem->type === 'mural' && $firstItem->width && $firstItem->height) { + $printSize = $firstItem->width . 'cm × ' . $firstItem->height . 'cm'; + } + } + + // Populate custom fields + $this->trello->setTextField($cardId, 'order_id', $order->uuid); + $this->trello->setTextField($cardId, 'design_name', $designName); + $this->trello->setTextField($cardId, 'customer_name', $order->customer_name ?? '—'); + $this->trello->setTextField($cardId, 'print_size', $printSize); + Log::info('Trello card created for order', ['order_uuid' => $order->uuid, 'card_id' => $cardId]); } } diff --git a/app/Services/TrelloService.php b/app/Services/TrelloService.php index e78b538..bf1a72e 100644 --- a/app/Services/TrelloService.php +++ b/app/Services/TrelloService.php @@ -4,6 +4,7 @@ namespace App\Services; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; +use DateTimeInterface; class TrelloService { @@ -14,24 +15,26 @@ class TrelloService public function __construct() { - $this->apiKey = config('trello.api_key'); + $this->apiKey = config('trello.api_key'); $this->apiToken = config('trello.api_token'); - $this->boardId = config('trello.board_id'); + $this->boardId = config('trello.board_id'); } - /** - * Create a new card on the board - */ - public function createCard(string $orderId, string $orderNumber, string $orderType = 'standard', ?string $startingListId = null): ?string - { - if (! $this->isConfigured()) { - Log::warning('Trello not configured, skipping card creation', [ - 'order_id' => $orderId, - 'api_key' => $this->apiKey ? 'set' : 'empty', - 'api_token' => $this->apiToken ? 'set' : 'empty', - 'board_id' => $this->boardId ? 'set' : 'empty', - ]); + /* ===================================================== + | Public API + ===================================================== */ + /** + * Create a new Trello card and populate base fields + */ + public function createCard( + string $orderId, + string $orderNumber, + string $orderType = 'standard', + ?string $startingListId = null + ): ?string { + if (! $this->isConfigured()) { + Log::warning('Trello not configured, skipping card creation'); return null; } @@ -39,31 +42,38 @@ class TrelloService $listId = $startingListId ?? $this->getStartingListId($orderType); $response = Http::post("{$this->baseUrl}/cards", [ - 'name' => "Order #{$orderNumber}", - 'desc' => "Order ID: {$orderId}\nType: {$orderType}", + 'name' => "Order #{$orderNumber}", + 'desc' => "Order ID: {$orderId}\nType: {$orderType}", 'idList' => $listId, - 'key' => $this->apiKey, - 'token' => $this->apiToken, + 'key' => $this->apiKey, + 'token' => $this->apiToken, ]); - if ($response->successful()) { - $data = $response->json(); - - return $data['id'] ?? null; + if (! $response->successful()) { + Log::error('Failed to create Trello card', [ + 'response' => $response->body(), + ]); + return null; } - Log::error('Failed to create Trello card', ['response' => $response->body()]); + $cardId = $response->json('id'); - return null; - } catch (\Exception $e) { - Log::error('Exception creating Trello card', ['error' => $e->getMessage()]); + if ($cardId) { + $this->populateInitialFields($cardId, $orderId); + } + return $cardId; + + } catch (\Throwable $e) { + Log::error('Exception creating Trello card', [ + 'error' => $e->getMessage(), + ]); return null; } } /** - * Move a card to a different list + * Move card to another list */ public function moveCard(string $cardId, string $listName): bool { @@ -71,131 +81,133 @@ class TrelloService return false; } - try { - $listId = $this->getListIdByName($listName); - - if (! $listId) { - Log::warning('Trello list not found', ['list_name' => $listName]); - - return false; - } - - $response = Http::put("{$this->baseUrl}/cards/{$cardId}", [ - 'idList' => $listId, - 'key' => $this->apiKey, - 'token' => $this->apiToken, - ]); - - return $response->successful(); - } catch (\Exception $e) { - Log::error('Exception moving Trello card', [ - 'card_id' => $cardId, - 'error' => $e->getMessage(), - ]); + $listId = $this->getListIdByName($listName); + if (! $listId) { + Log::warning('Trello list not found', ['list' => $listName]); return false; } + + $response = Http::put("{$this->baseUrl}/cards/{$cardId}", [ + 'idList' => $listId, + 'key' => $this->apiKey, + 'token' => $this->apiToken, + ]); + + return $response->successful(); } /** - * Attach a file or URL to a card + * Attach a URL/file to a card */ - public function attachFile(string $cardId, string $fileName, string $fileUrl): bool + public function attachFile(string $cardId, string $name, string $url): bool { - if (! $this->isConfigured()) { - return false; - } + $response = Http::post("{$this->baseUrl}/cards/{$cardId}/attachments", [ + 'name' => $name, + 'url' => $url, + 'key' => $this->apiKey, + 'token' => $this->apiToken, + ]); - try { - $response = Http::post("{$this->baseUrl}/cards/{$cardId}/attachments", [ - 'name' => $fileName, - 'url' => $fileUrl, - 'key' => $this->apiKey, - 'token' => $this->apiToken, - ]); - - return $response->successful(); - } catch (\Exception $e) { - Log::error('Exception attaching file to Trello card', [ - 'card_id' => $cardId, - 'error' => $e->getMessage(), - ]); - - return false; - } + return $response->successful(); } /** - * Check/tick a checklist item on a card + * Mark checklist item complete */ public function checkItem(string $cardId, string $checklistName, string $itemName): bool { - if (! $this->isConfigured()) { + $card = Http::get("{$this->baseUrl}/cards/{$cardId}", [ + 'checklists' => 'open', + 'key' => $this->apiKey, + 'token' => $this->apiToken, + ])->json(); + + $checklist = collect($card['checklists'] ?? []) + ->firstWhere('name', $checklistName); + + if (! $checklist) { return false; } - try { - // Fetch card to find checklist - $cardResponse = Http::get("{$this->baseUrl}/cards/{$cardId}", [ - 'key' => $this->apiKey, - 'token' => $this->apiToken, - 'checklists' => 'open', - ]); + $item = collect($checklist['checkItems'] ?? []) + ->firstWhere('name', $itemName); - if (! $cardResponse->successful()) { - return false; - } + if (! $item) { + return false; + } - $checklists = $cardResponse->json('checklists') ?? []; - $checklist = collect($checklists)->firstWhere('name', $checklistName); - - if (! $checklist) { - Log::warning('Trello checklist not found', ['checklist_name' => $checklistName]); - - return false; - } - - $checklistId = $checklist['id']; - $item = collect($checklist['checkItems'])->firstWhere('name', $itemName); - - if (! $item) { - Log::warning('Trello checklist item not found', ['item_name' => $itemName]); - - return false; - } - - $response = Http::put("{$this->baseUrl}/checklists/{$checklistId}/checkItems/{$item['id']}", [ + $response = Http::put( + "{$this->baseUrl}/checklists/{$checklist['id']}/checkItems/{$item['id']}", + [ 'state' => 'complete', - 'key' => $this->apiKey, + 'key' => $this->apiKey, 'token' => $this->apiToken, - ]); + ] + ); - return $response->successful(); - } catch (\Exception $e) { - Log::error('Exception checking Trello item', [ - 'card_id' => $cardId, - 'error' => $e->getMessage(), - ]); - - return false; - } + return $response->successful(); + } + + /* ===================================================== + | Custom Fields + ===================================================== */ + + public function setTextField(string $cardId, string $fieldKey, string $value): bool + { + return $this->setCustomField($cardId, $fieldKey, ['text' => $value]); + } + + public function setNumberField(string $cardId, string $fieldKey, float|int $value): bool + { + return $this->setCustomField($cardId, $fieldKey, ['number' => (string) $value]); + } + + public function setDateField(string $cardId, string $fieldKey, DateTimeInterface $date): bool + { + return $this->setCustomField($cardId, $fieldKey, [ + 'date' => $date->format(DATE_ATOM), + ]); + } + + protected function setCustomField(string $cardId, string $fieldKey, array $value): bool + { + $fieldId = config("trello.custom_fields.{$fieldKey}"); + + if (! $fieldId) { + Log::warning('Trello custom field not configured', ['field' => $fieldKey]); + return false; + } + + $response = Http::put( + "{$this->baseUrl}/cards/{$cardId}/customField/{$fieldId}/item", + [ + 'value' => $value, + 'key' => $this->apiKey, + 'token' => $this->apiToken, + ] + ); + + return $response->successful(); + } + + /* ===================================================== + | Internals + ===================================================== */ + + protected function populateInitialFields(string $cardId, string $orderId): void + { + $this->setTextField($cardId, 'order_id', $orderId); + $this->setDateField($cardId, 'date_created', now()); } - /** - * Get starting list ID for order type - */ protected function getStartingListId(string $orderType): string { - if ($orderType === 'custom') { - return config('trello.lists.custom.new_custom_order'); - } - - return config('trello.lists.standard.new_order'); + return $orderType === 'custom' + ? config('trello.lists.custom.new_custom_order') + : config('trello.lists.standard.new_order'); } - /** - * Get list ID by name (from config) - */ protected function getListIdByName(string $listName): ?string { $lists = array_merge( @@ -204,7 +216,7 @@ class TrelloService ); foreach ($lists as $key => $id) { - if (str_replace('_', ' ', ucfirst($key)) === $listName) { + if (strtolower(str_replace('_', ' ', $key)) === strtolower($listName)) { return $id; } } @@ -212,11 +224,10 @@ class TrelloService return null; } - /** - * Check if Trello is configured - */ protected function isConfigured(): bool { - return ! empty($this->apiKey) && ! empty($this->apiToken) && ! empty($this->boardId); + return ! empty($this->apiKey) + && ! empty($this->apiToken) + && ! empty($this->boardId); } } diff --git a/config/trello.php b/config/trello.php index efce561..54370cf 100644 --- a/config/trello.php +++ b/config/trello.php @@ -21,6 +21,20 @@ return [ 'board_id' => env('TRELLO_BOARD_ID') ?: ($env['TRELLO_BOARD_ID'] ?? null), 'webhook_secret' => env('TRELLO_WEBHOOK_SECRET') ?: ($env['TRELLO_WEBHOOK_SECRET'] ?? null), + 'custom_fields' => [ + 'order_id' => '695792226064b67fb4bd46f0', + 'design_name' => '69579233dbdfa1de96beef7f', + 'customer_name' => '6957924221e31eb4a7a271b0', + 'print_size' => '6957926dab26ae511aaf17e9', + + 'ship_w' => '6957929435cdaf8b318b16d5', + 'ship_h' => '695792a3c5ef01fd66d9bb66', + 'ship_l' => '695792bdba79b9499f0f5f59', + 'weight' => '695792c98bc7955d6bd796c0', + + 'date_created' => '695792ef2545efa98ef8dce7', + ], + 'lists' => [ 'standard' => [ 'new_order' => env('TRELLO_LIST_ID_NEW_ORDER') ?: ($env['TRELLO_LIST_ID_NEW_ORDER'] ?? null),