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:
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('orders', function (Blueprint $table) {
|
||||
// Packing data
|
||||
$table->decimal('packing_width', 8, 2)->nullable()->comment('Width in cm');
|
||||
$table->decimal('packing_length', 8, 2)->nullable()->comment('Length in cm');
|
||||
$table->decimal('packing_weight', 8, 2)->nullable()->comment('Weight in kg');
|
||||
$table->timestamp('packing_completed_at')->nullable();
|
||||
$table->foreignId('packed_by')->nullable()->constrained('users');
|
||||
|
||||
// Courier integration
|
||||
$table->string('courier_waybill_id')->nullable()->unique();
|
||||
$table->string('courier_tracking_number')->nullable();
|
||||
$table->string('courier_status')->nullable()->default('pending'); // pending, awaiting_collection, in_transit, delivered, failed
|
||||
$table->timestamp('delivered_at')->nullable();
|
||||
$table->text('delivery_failure_reason')->nullable();
|
||||
|
||||
// Trello integration
|
||||
$table->string('trello_card_id')->nullable();
|
||||
|
||||
// QR code (for later)
|
||||
$table->string('qr_token')->nullable()->unique();
|
||||
$table->timestamp('qr_generated_at')->nullable();
|
||||
|
||||
$table->index('courier_waybill_id');
|
||||
});
|
||||
|
||||
Schema::table('custom_orders', function (Blueprint $table) {
|
||||
// Packing data
|
||||
$table->decimal('packing_width', 8, 2)->nullable()->comment('Width in cm');
|
||||
$table->decimal('packing_length', 8, 2)->nullable()->comment('Length in cm');
|
||||
$table->decimal('packing_weight', 8, 2)->nullable()->comment('Weight in kg');
|
||||
$table->timestamp('packing_completed_at')->nullable();
|
||||
$table->foreignId('packed_by')->nullable()->constrained('users');
|
||||
|
||||
// Courier integration
|
||||
$table->string('courier_waybill_id')->nullable()->unique();
|
||||
$table->string('courier_tracking_number')->nullable();
|
||||
$table->string('courier_status')->nullable()->default('pending');
|
||||
$table->timestamp('delivered_at')->nullable();
|
||||
$table->text('delivery_failure_reason')->nullable();
|
||||
|
||||
// Trello integration
|
||||
$table->string('trello_card_id')->nullable();
|
||||
|
||||
// QR code (for later)
|
||||
$table->string('qr_token')->nullable()->unique();
|
||||
$table->timestamp('qr_generated_at')->nullable();
|
||||
|
||||
$table->index('courier_waybill_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('orders', function (Blueprint $table) {
|
||||
$table->dropForeign(['packed_by']);
|
||||
$table->dropIndex(['courier_waybill_id']);
|
||||
$table->dropColumn([
|
||||
'packing_width',
|
||||
'packing_length',
|
||||
'packing_weight',
|
||||
'packing_completed_at',
|
||||
'packed_by',
|
||||
'courier_waybill_id',
|
||||
'courier_tracking_number',
|
||||
'courier_status',
|
||||
'delivered_at',
|
||||
'delivery_failure_reason',
|
||||
'trello_card_id',
|
||||
'qr_token',
|
||||
'qr_generated_at',
|
||||
]);
|
||||
});
|
||||
|
||||
Schema::table('custom_orders', function (Blueprint $table) {
|
||||
$table->dropForeign(['packed_by']);
|
||||
$table->dropIndex(['courier_waybill_id']);
|
||||
$table->dropColumn([
|
||||
'packing_width',
|
||||
'packing_length',
|
||||
'packing_weight',
|
||||
'packing_completed_at',
|
||||
'packed_by',
|
||||
'courier_waybill_id',
|
||||
'courier_tracking_number',
|
||||
'courier_status',
|
||||
'delivered_at',
|
||||
'delivery_failure_reason',
|
||||
'trello_card_id',
|
||||
'qr_token',
|
||||
'qr_generated_at',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user