fix: Extract design_name and print_size from order items for Trello
- design_name: Get product name from first order item
- print_size: Calculate from item dimensions (wallpaper: length in m, mural: width x height in cm)
- Add fallback values ('') when items/dimensions unavailable
- Prevents null errors when populating Trello custom fields
This commit is contained in:
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
+139
-128
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user