baks-dev/orders-order
Laravel/Symfony модуль системных заказов: статусы заказов через OrderStatusInterface и тег baks.order.status, асинхронная обработка через Messenger воркер orders-order, интеграция с Centrifugo, миграции Doctrine, установка ресурсов baks:assets:install.
Install Dependencies:
composer require baks-dev/orders-order baks-dev/payment baks-dev/users-address baks-dev/contacts-region baks-dev/centrifugo baks-dev/products-stocks baks-dev/delivery phpoffice/phpspreadsheet
Note: Ensure php >= 8.4 and Laravel 10+ compatibility.
Run Asset Installation:
php bin/console baks:assets:install
Purpose: Installs default configurations, migrations, and resources.
Set Up Database:
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Verify: Check migrations/ for generated SQL and inspect the orders table structure.
Launch Messenger Worker:
php bin/console messenger:consume orders-order
Why: Async processing for order events (e.g., status changes, inventory updates).
Test a Basic Order:
use BaksDev\Orders\Order\Application\Service\OrderService;
$orderService = app(OrderService::class);
$order = $orderService->create([
'user_id' => 1,
'items' => [['product_id' => 1, 'quantity' => 2]],
]);
Expected: Order created with default status (e.g., pending).
Goal: Add a "Hold for Approval" status to orders requiring manual review.
Create a Status Class:
// app/Orders/OrderStatus/ApprovalHoldStatus.php
namespace App\Orders\OrderStatus;
use BaksDev\Orders\Order\Type\Status\OrderStatus\OrderStatusInterface;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
#[AutoconfigureTag('baks.order.status')]
class ApprovalHoldStatus implements OrderStatusInterface
{
public function getName(): string
{
return 'approval_hold';
}
public function canTransitionTo(string $toStatus): bool
{
// Allow transitions to 'approved' or 'cancelled'
return in_array($toStatus, ['approved', 'cancelled']);
}
public function getTransitions(): array
{
return [
'approved' => 'Approve Order',
'cancelled' => 'Cancel Order',
];
}
}
Trigger the Status:
$orderService->transitionStatus($order->getId(), 'approval_hold');
Verify: Check the database for the new status and ensure transitions are enforced.
BaksDev\Orders\Order\Application\Service\OrderService (core CRUD + workflows).BaksDev\Orders\Order\Type\Status\OrderStatus (interfaces and base implementations).BaksDev\Orders\Order\Domain\Event (listen for OrderCreated, StatusChanged, etc.).BaksDev\Centrifugo\Client (real-time updates; see config/centrifugo.php).Creation:
$order = $orderService->create([
'user_id' => $userId,
'items' => [
['product_id' => 1, 'quantity' => 3],
['product_id' => 2, 'quantity' => 1],
],
'delivery_address_id' => $addressId,
]);
Triggers: OrderCreated event → async inventory reservation (products-stocks).
Status Transitions:
// Sync transition
$orderService->transitionStatus($orderId, 'shipped');
// Async transition (recommended for long-running tasks)
$orderService->transitionStatusAsync($orderId, 'shipped');
Hooks: Use event listeners (e.g., OrderStatusChanged) to notify users or update delivery systems.
Cancellation:
$orderService->cancel($orderId, 'Customer requested cancellation');
Behavior: Releases inventory, refunds payments (if integrated with baks-dev/payment), and notifies stakeholders.
Leverage the products-stocks dependency to auto-reserve/release stock:
// Listen for OrderCreated
event(new OrderCreated($order));
// In the event handler:
$stockService = app(\BaksDev\Products\Stocks\Application\Service\StockService::class);
foreach ($order->getItems() as $item) {
$stockService->reserve(
$item->getProductId(),
$item->getQuantity(),
$order->getId()
);
}
Use the delivery package to create shipments when orders are shipped:
// Listen for OrderStatusChanged
if ($event->getToStatus() === 'shipped') {
$deliveryService = app(\BaksDev\Delivery\Application\Service\DeliveryService::class);
$deliveryService->createShipment($order->getId(), $order->getDeliveryAddress());
}
Push order status changes to clients via Centrifugo:
// In your OrderStatusChanged listener:
$centrifugo = app(\BaksDev\Centrifugo\Client);
$centrifugo->publish(
"order_updates:{$order->getId()}",
['status' => $event->getToStatus(), 'timestamp' => now()]
);
Frontend: Subscribe to channels in your JS app:
const channel = centrifugo.newChannel('order_updates:123');
channel.on('update', (data) => {
console.log('Order status:', data.status);
});
Offload heavy tasks (e.g., email notifications, ERP sync) to Messenger:
// Dispatch a job
$orderNotification = new SendOrderNotification($order);
app(\Symfony\Component\Messenger\MessageBusInterface::class)->dispatch($orderNotification);
Order, OrderItem, OrderStatus (immutable domain objects).OrderId, Money, Address (encapsulated logic).OrderRepository (abstracts persistence; extend for custom queries).OrderCreated, StatusChanged, PaymentProcessed.// app/Listeners/OrderCreatedListener.php
class OrderCreatedListener
{
public function __invoke(OrderCreated $event)
{
// Send welcome email, update analytics, etc.
}
}
Extend functionality via OrderStatusInterface:
// Custom status for subscriptions
class SubscriptionPauseStatus implements OrderStatusInterface
{
public function getName(): string { return 'subscription_paused'; }
public function canTransitionTo(string $toStatus): bool
{
return $toStatus === 'subscription_resumed' || $toStatus === 'cancelled';
}
}
Unit Tests:
public function testApprovalHoldStatusTransitions()
{
$status = new ApprovalHoldStatus();
$this->assertTrue($status->canTransitionTo('approved'));
$this->assertFalse($status->canTransitionTo('shipped'));
}
Feature Tests:
public function testOrderWorkflow()
{
$order = $this->createOrder();
$this->assertEquals('pending', $order->getStatus());
$this->transitionStatus($order, 'approval_hold');
$this->assertEquals('approval_hold', $order->getStatus());
$this->transitionStatus($order, 'approved');
$this->assertEquals('approved', $order->getStatus());
}
Messenger Tests:
public function testAsyncStatusTransition()
{
$this->artisan('messenger:consume orders-order --time-limit=1')
->expectsOutputToContain('Order status updated');
$this->transitionStatusAsync($order, 'shipped');
$this->assertDatabaseHas('orders', ['status' => 'shipped']);
}
pending → shipped without paid).canTransitionTo() and test exhaustively:
public function canTransitionTo(string $toStatus): bool
{
$
How can I help you explore Laravel packages today?