Aventux
Docs

Extension Development Guide

This guide explains how to build paid extension plugins for the AventDropshippingSuite Core.

Creating an Extension Plugin

1. composer.json

{
    "name": "avent/dropshipping-supplier-connect",
    "type": "shopware-platform-plugin",
    "require": {
        "shopware/core": "~6.7.0",
        "avent/dropshipping-suite": "~1.0"
    },
    "autoload": {
        "psr-4": {
            "Avent\\DropshippingSupplierConnect\\": "src/"
        }
    },
    "extra": {
        "shopware-plugin-class": "Avent\\DropshippingSupplierConnect\\AventDropshippingSupplierConnect"
    }
}

2. Decorating Core Interfaces

Extensions modify Core behavior by decorating interfaces:

<!-- services.xml -->
<service id="Avent\DropshippingAutoOrder\Service\AutoOrderProcessor"
         decorates="Avent\DropshippingSuite\Interface\DropshipOrderProcessorInterface"
         decoration-priority="100">
    <argument type="service" id=".inner"/>
    <argument type="service" id="event_dispatcher"/>
</service>
class AutoOrderProcessor implements DropshipOrderProcessorInterface
{
    public function __construct(
        private readonly DropshipOrderProcessorInterface $innerProcessor,
        private readonly EventDispatcherInterface $eventDispatcher,
    ) {}

    public function process(OrderEntity $order, Context $context): array
    {
        // Call the core processor first
        $dropshipOrders = $this->innerProcessor->process($order, $context);

        // Add extension logic (e.g., send notifications)
        foreach ($dropshipOrders as $dropshipOrder) {
            $this->sendToSupplier($dropshipOrder, $context);
        }

        return $dropshipOrders;
    }
}

3. Listening to Core Events

<service id="Avent\DropshippingTracking\Subscriber\DropshipOrderSubscriber">
    <argument type="service" id="avent_dropship_order.repository"/>
    <tag name="kernel.event_subscriber"/>
</service>
class DropshipOrderSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            DropshipOrderCreatedEvent::class => 'onCreated',
            DropshipOrderStatusChangedEvent::class => 'onStatusChanged',
            SupplierSelectedEvent::class => 'onSupplierSelected',
        ];
    }

    public function onCreated(DropshipOrderCreatedEvent $event): void
    {
        // Initialize tracking entry for new dropship order
    }

    public function onStatusChanged(DropshipOrderStatusChangedEvent $event): void
    {
        if ($event->getNewStatus() === 'shipped') {
            // Send tracking notification to customer
        }
    }

    public function onSupplierSelected(SupplierSelectedEvent $event): void
    {
        // Override supplier selection based on stock availability
        $supplier = $this->checkStockAndSelectBest($event->getProductId());
        if ($supplier) {
            $event->setSupplier($supplier);
        }
    }
}

4. Optional Dependencies Between Extensions

Use on-invalid="null" for optional dependencies:

<!-- AutoOrder can optionally use SupplierConnect's adapter -->
<service id="Avent\DropshippingAutoOrder\Service\NotificationService">
    <argument type="service"
             id="Avent\DropshippingSupplierConnect\Adapter\AdapterRegistry"
             on-invalid="null"/>
</service>
class NotificationService
{
    public function __construct(
        private readonly ?AdapterRegistry $adapterRegistry,
    ) {}

    public function notify(SupplierEntity $supplier, DropshipOrderEntity $order): void
    {
        if ($this->adapterRegistry !== null) {
            // Use API adapter from SupplierConnect
            $adapter = $this->adapterRegistry->getAdapter($supplier->getId());
            $adapter->sendOrder($order);
        } else {
            // Fallback to email
            $this->sendEmailNotification($supplier, $order);
        }
    }
}

5. Registering CSV Mapping Types

Extensions register their mapping types by providing target field definitions:

// In SupplierConnect: register 'product_import' type
$targetFields = [
    ['value' => 'supplier_sku', 'label' => 'Supplier SKU'],
    ['value' => 'purchase_price', 'label' => 'Purchase Price'],
    ['value' => 'stock', 'label' => 'Stock Quantity'],
    ['value' => 'ean', 'label' => 'EAN'],
    ['value' => 'name', 'label' => 'Product Name'],
];

6. Using Core CSV Components in Extension Admin UI

<!-- In extension admin component template -->
<avent-csv-upload @file-loaded="onFileLoaded" />

<avent-csv-column-mapper
    v-if="csvHeaders.length > 0"
    :source-columns="csvHeaders"
    :target-fields="productImportFields"
    :mapping="currentMapping"
    @mapping-changed="onMappingChanged"
/>

<avent-csv-transformer
    v-if="currentMapping.length > 0"
    :fields="mappedFields"
    :transformations="transformations"
    @transformations-changed="onTransformationsChanged"
/>

<avent-csv-preview
    v-if="csvRows.length > 0"
    :rows="csvRows"
    :mapping="currentMapping"
/>

<avent-csv-profile-manager
    :supplier-id="supplierId"
    profile-type="product_import"
    @profile-loaded="onProfileLoaded"
    @save-profile="onSaveProfile"
/>

Extension Architecture

Decorator Chain (Service Decoration)

DropshipOrderProcessorInterface
  +-- AutoOrder Decorator (priority 100) -- Splitting + Notification
       +-- Tracking Decorator (priority 50) -- Status Management
            +-- Core Default Implementation (DropshipOrderService)

Higher priority decorators run first (outermost). Each decorator calls $this->innerProcessor->process() to delegate to the next in the chain.

Event Flow

Customer places order
    |
    v
OrderPlacedSubscriber (Core)
    |
    v
DropshipOrderProcessorInterface::process()
    |  (decorated by AutoOrder, Tracking)
    |
    v
SupplierSelectionService::selectSupplier()
    |
    v
SupplierSelectedEvent  <-- Extensions can override supplier
    |
    v
DropshipOrder created in DB
    |
    v
DropshipOrderCreatedEvent
    |-- AutoOrder listens: sends to supplier
    +-- Tracking listens: initializes tracking status

Example Extension: AventDropshippingSupplierConnect

Entities

  • avent_ds_import_profile - Adapter-specific configuration (REST auth, endpoints)
  • avent_ds_import_log - Import log with status and error tracking

Key Services

  • ImportService - Implements SupplierImportInterface
  • AdapterRegistry - Manages REST API adapters per supplier
  • StockSyncService - Scheduled stock synchronization

Admin UI

  • Import profile management per supplier
  • Sync log viewer
  • Navigation under "Dropshipping > Lieferanten-Anbindung"

Testing Extensions

// In PHPUnit tests, mock Core interfaces:
$selectionStrategy = $this->createMock(SupplierSelectionStrategyInterface::class);
$selectionStrategy->method('selectSupplier')
    ->willReturn($mockSupplier);

$orderProcessor = new YourExtensionProcessor(
    $coreProcessor,
    $eventDispatcher,
    $selectionStrategy,
);

$result = $orderProcessor->process($mockOrder, Context::createDefaultContext());