feat: Implement Slack, Trello, and Courier (ShipLogic) integration

- Add 14 domain events for order lifecycle (OrderCreated, OrderPacked, ShipmentCreated, ParcelDelivered, etc.)
- Create SlackNotifierService with channels for orders, design, production, shipping, ops-alerts
- Create TrelloService to create cards, move cards between lists, attach files, check items
- Create CourierService to integrate with ShipLogic API for shipment creation and document retrieval
- Create PackingController to explicitly capture packing dimensions and weight
- Create ShippingController with multi-layer guards: packing validation, payment/approval verification, idempotency
- Create TrelloWebhookController to handle incoming Trello webhooks as intent signals
- Create CourierWebhookController to handle Shiplogic status updates
- Create event listeners for Slack notifications and Trello card updates
- Create EventServiceProvider to register all events and listeners
- Add database migration for packing, courier, and Trello data columns
- Create config files for slack, trello, and courier integration
- Update .env with integration secrets placeholders
- Add routes for /orders/{id}/pack, /orders/{id}/ship, /api/webhooks/trello, /api/webhooks/courier

Key architectural decisions:
- Packing is explicit ops action (not automatic from status)
- Shipment creation only after: Trello intent + packing confirmed + payment/approval rules met
- Courier API failures keep order in Ready to Ship state (safe retry)
- Trello and Slack are mirrors of backend state, not decision makers
- All side effects flow through event listeners, maintaining separation of concerns
This commit is contained in:
twotalesanimation
2026-01-02 13:47:31 +02:00
parent 4730f5770c
commit 12aadfd917
35 changed files with 1815 additions and 0 deletions
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class BalancePaid
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
public float $balanceAmount,
) {
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class DepositPaid
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
public float $depositAmount,
) {
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public Order $order,
public string $orderType = 'standard', // 'standard' or 'custom'
) {
}
public function broadcastOn(): array
{
return [
new PrivateChannel('channel-name'),
];
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderPacked
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
public float $width,
public float $length,
public float $weight,
public ?int $packedBy = null,
) {
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ParcelCollected
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
public string $waybillId,
) {
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ParcelDelivered
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
public string $waybillId,
) {
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ParcelFailedDelivery
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
public string $waybillId,
public string $failureReason = '',
) {
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ParcelInTransit
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
public string $waybillId,
) {
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Events;
use App\Models\CustomOrder;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ProofApproved
{
use Dispatchable, SerializesModels;
public function __construct(
public CustomOrder $customOrder,
) {
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Events;
use App\Models\CustomOrder;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ProofRevisionRequested
{
use Dispatchable, SerializesModels;
public function __construct(
public CustomOrder $customOrder,
public string $revisionNotes = '',
) {
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Events;
use App\Models\CustomOrder;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ProofUploaded
{
use Dispatchable, SerializesModels;
public function __construct(
public CustomOrder $customOrder,
public string $proofFilePath,
) {
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ReadyToShipIntent
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
public ?string $trelloCardId = null,
) {
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ShipmentCreated
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
public string $waybillId,
public string $trackingNumber,
public ?string $stickerPath = null,
public ?string $waybillPath = null,
) {
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ShipmentCreationFailed
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order,
public string $errorMessage,
) {
}
}