Ana içeriğe geç
Yılmaz Soft

Magento 2 ERP ve Muhasebe Entegrasyon Rehberi (Logo, Netsis, SAP vb.)

Magento 2, e-ticaret altyapısı olarak ne kadar güçlü olsa da, operasyonel kalp genellikle ERP ve Muhasebe yazılımlarıdır. Bu rehber ERP entegrasyon modülü oluşturmanın temel adımlarını anlatır.

Yılmaz Soft
7 dk okuma

Magento 2, e‑ticaret altyapısı olarak ne kadar güçlü olsa da, işletmenin operasyonel kalbi genellikle ERP (Kurumsal Kaynak Planlama) ve Muhasebe yazılımlarıdır. Stok yönetimi, sipariş takibi, faturalandırma, müşteri verisi ve finansal raporlama gibi kritik süreçler bu sistemlerde yönetilir.

Bu iki dünya arasındaki (E‑Ticaret ve ERP) manuel veri girişi yapmak, hem zaman kaybıdır hem de insan hatasına açıktır. Çözüm, bu sistemleri birbirine bağlayan sağlam bir entegrasyon modülüdür.

Ancak, bir ERP entegrasyonu geliştirmek, Magento 2’deki en karmaşık görevlerden biridir. Sadece Magento mimarisini değil, aynı zamanda bağlanılacak ERP’nin API’sini (SOAP, REST, XML, dosya transferi vb.), veri modellerini ve iş akışlarını da derinlemesine anlamayı gerektirir.

Bu rehberde, bir Magento 2 ERP “Connector” (Bağlayıcı) modülünün temel konseptlerini, kullanılacak ana araçları (Observers ve Cron Jobs) ve basit kod örneklerini bulacaksınız.

Önemli Not: Bu rehber, temel yapı ve konseptleri göstermek amacıyla hazırlanmıştır. Gerçek dünyada bir ERP entegrasyonu (Logo, Netsis, SAP, Mikro, özel yazılımlar vb.) iki yönlü veri akışı, çakışma (conflict) yönetimi, performans optimizasyonu (binlerce ürünü senkronize etmek) ve detaylı hata takibi gibi daha karmaşık senaryolar içerir.

1. Adım: Entegrasyon Stratejisi ve Modül Yapısı

Önce neyi, ne zaman ve nasıl senkronize edeceğimize karar vermeliyiz. Örnek olarak:

  • Siparişler: Yeni bir sipariş alındığında anında ERP’ye aktarılmalı mı? (Gerçek zamanlı)
  • Stok: ERP’deki stok değiştiğinde Magento’ya ne sıklıkta yansıtılmalı? (Toplu, örn: saatlik)
  • Müşteriler: Yeni üye olan müşteri ERP’de de oluşturulmalı mı? (Gerçek zamanlı)

Bu rehberde, “Sipariş anında gönder” (Observer kullanarak) ve “Stok saatlik güncelle” (Cron Job kullanarak) senaryolarına odaklanacağız.

Modülümüzün adı YilmazSoft_ErpConnector olsun.

registration.php

<?php
/**
 * Copyright © Yılmaz Soft. All rights reserved.
 */
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(
    ComponentRegistrar::MODULE,
    'YilmazSoft_ErpConnector',
    __DIR__
);

etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="YilmazSoft_ErpConnector" setup_version="1.0.0">
        <sequence>
            <module name="Magento_Sales"/>
            <module name="Magento_Catalog"/>
            <module name="Magento_Customer"/>
        </sequence>
    </module>
</config>

2. Adım: Yapılandırma (Admin Panel Ayarları)

Entegrasyonun çalışması için API bilgileri ve ayarlar gereklidir. Admin panelde “Mağazalar > Yapılandırma” altına kendi ayarlar sayfanızı ekleyin.

etc/adminhtml/system.xml (örnek — kısaltılmış)

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <tab id="yilmazsoft" translate="label" sortOrder="100">
            <label>Yılmaz Soft</label>
        </tab>
        <section id="erpconnector" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
            <label>ERP Entegrasyonu</label>
            <tab>yilmazsoft</tab>
            <resource>YilmazSoft_ErpConnector::config</resource>
            <group id="general" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Genel Ayarlar</label>
                <field id="enable" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Entegrasyon Aktif</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="api_endpoint" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>ERP API Adresi (Endpoint)</label>
                    <comment>Örn: https://erp.sirketim.com/api/v2/</comment>
                </field>
                <field id="api_key" translate="label" type="obscure" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>API Anahtarı (Token/Key)</label>
                    <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
                </field>
            </group>
            <group id="sync" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Senkronizasyon Ayarları</label>
                <field id="sync_orders" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Siparişleri Senkronize Et</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="sync_stock" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0">
                    <label>Stokları Senkronize Et</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
            </group>
        </section>
    </system>
</config>

3. Adım: Gerçek Zamanlı Sipariş Aktarımı (Observer)

Müşteri “Siparişi Tamamla” butonuna bastıktan hemen sonra siparişi ERP’ye göndereceğiz. Bunun için sales_order_place_after event’ini dinleyin.

etc/events.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="sales_order_place_after">
        <observer name="yilmazsoft_erpconnector_order_place_after" instance="YilmazSoft\ErpConnector\Observer\OrderPlaceAfter"/>
    </event>
</config>

Observer: OrderPlaceAfter.php (basitleştirilmiş örnek)

<?php

namespace YilmazSoft\ErpConnector\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\HTTP\Client\Curl;
use Magento\Framework\Serialize\Serializer\Json;
use Psr\Log\LoggerInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;

class OrderPlaceAfter implements ObserverInterface
{
    protected $logger;
    protected $curl;
    protected $jsonSerializer;
    protected $scopeConfig;

    const XML_PATH_ERP_ENABLE = 'erpconnector/general/enable';
    const XML_PATH_ERP_API_ENDPOINT = 'erpconnector/general/api_endpoint';
    const XML_PATH_ERP_API_KEY = 'erpconnector/general/api_key';
    const XML_PATH_ERP_SYNC_ORDERS = 'erpconnector/sync/sync_orders';

    public function __construct(
        LoggerInterface $logger,
        Curl $curl,
        Json $jsonSerializer,
        ScopeConfigInterface $scopeConfig
    ) {
        $this->logger = $logger;
        $this->curl = $curl;
        $this->jsonSerializer = $jsonSerializer;
        $this->scopeConfig = $scopeConfig;
    }

    public function execute(Observer $observer)
    {
        $storeScope = \Magento\Store\Model\ScopeInterface::SCOPE_STORE;
        $isEnabled = $this->scopeConfig->getValue(self::XML_PATH_ERP_ENABLE, $storeScope);
        $isOrderSyncEnabled = $this->scopeConfig->getValue(self::XML_PATH_ERP_SYNC_ORDERS, $storeScope);

        if (!$isEnabled || !$isOrderSyncEnabled) {
            $this->logger->info('ERP Connector: Sipariş gönderimi pasif.');
            return $this;
        }

        try {
            $order = $observer->getEvent()->getOrder();
            if (!$order) return $this;

            $apiEndpoint = $this->scopeConfig->getValue(self::XML_PATH_ERP_API_ENDPOINT, $storeScope);
            $apiKey = $this->scopeConfig->getValue(self::XML_PATH_ERP_API_KEY, $storeScope);

            $orderData = [
                'order_id' => $order->getIncrementId(),
                'total' => $order->getGrandTotal(),
                'customer_email' => $order->getCustomerEmail(),
                'items' => []
            ];

            foreach ($order->getAllItems() as $item) {
                $orderData['items'][] = [
                    'sku' => $item->getSku(),
                    'qty' => $item->getQtyOrdered(),
                    'price' => $item->getPrice()
                ];
            }

            $jsonBody = $this->jsonSerializer->serialize($orderData);

            $this->curl->setOption(CURLOPT_TIMEOUT, 10);
            $this->curl->addHeader("Content-Type", "application/json");
            $this->curl->addHeader("Authorization", "Bearer " . $apiKey);

            $this->curl->post($apiEndpoint . 'orders', $jsonBody);
            $response = $this->curl->getBody();
            $httpStatusCode = $this->curl->getStatus();

            if ($httpStatusCode == 200 || $httpStatusCode == 201) {
                $this->logger->info('ERP Connector: Sipariş başarıyla gönderildi: ' . $order->getIncrementId());
                $order->addStatusHistoryComment('ERP sistemine başarıyla gönderildi. (Ref: ' . $httpStatusCode . ')');
                $order->save();
            } else {
                throw new \Exception("ERP API Hatası: " . $httpStatusCode . " - Cevap: " . $response);
            }
        } catch (\Exception $e) {
            $this->logger->critical('ERP Connector: Sipariş gönderim hatası: ' . $e->getMessage());
            if (isset($order)) {
                $order->addStatusHistoryComment('ERP SİSTEMİNE GÖNDERİLEMEDİ. Hata: ' . $e->getMessage());
                $order->save();
            }
        }

        return $this;
    }
}

Not: Gerçekte bu işlemi bir Queue (RabbitMQ) mekanizmasına eklemek, cevap gecikmeleri ya da kesintilerde daha güvenilir olur.

4. Adım: Toplu Stok Senkronizasyonu (Cron Job)

ERP’deki stokları Magento’ya çekmek için Cron Job kullanın. Aşağıdaki örnek her saat başı çalışan bir job tanımlar.

etc/crontab.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Cron:etc/crontab.xsd">
    <group id="default">
        <job name="yilmazsoft_erpconnector_stocksync" instance="YilmazSoft\ErpConnector\Cron\StockSync" method="execute">
            <!-- Her saat başı çalışır -->
            <schedule>0 * * * *</schedule>
        </job>
    </group>
</config>

Cron class: StockSync.php (taslak-ve örnek)

<?php

namespace YilmazSoft\ErpConnector\Cron;

use Psr\Log\LoggerInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\HTTP\Client\Curl;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;

class StockSync
{
    protected $logger;
    protected $scopeConfig;
    protected $curl;
    protected $jsonSerializer;
    protected $stockRegistry;
    protected $productRepository;

    const XML_PATH_ERP_ENABLE = 'erpconnector/general/enable';
    const XML_PATH_ERP_API_ENDPOINT = 'erpconnector/general/api_endpoint';
    const XML_PATH_ERP_API_KEY = 'erpconnector/general/api_key';
    const XML_PATH_ERP_SYNC_STOCK = 'erpconnector/sync/sync_stock';

    public function __construct(
        LoggerInterface $logger,
        ScopeConfigInterface $scopeConfig,
        Curl $curl,
        Json $jsonSerializer,
        StockRegistryInterface $stockRegistry,
        ProductRepositoryInterface $productRepository
    ) {
        $this->logger = $logger;
        $this->scopeConfig = $scopeConfig;
        $this->curl = $curl;
        $this->jsonSerializer = $jsonSerializer;
        $this->stockRegistry = $stockRegistry;
        $this->productRepository = $productRepository;
    }

    public function execute()
    {
        $this->logger->info('ERP Connector: Stok Senkronizasyonu Cron Başladı.');

        $storeScope = \Magento\Store\Model\ScopeInterface::SCOPE_STORE;
        $isEnabled = $this->scopeConfig->getValue(self::XML_PATH_ERP_ENABLE, $storeScope);
        $isStockSyncEnabled = $this->scopeConfig->getValue(self::XML_PATH_ERP_SYNC_STOCK, $storeScope);

        if (!$isEnabled || !$isStockSyncEnabled) {
            $this->logger->info('ERP Connector: Stok senkronizasyonu pasif.');
            return;
        }

        try {
            $apiEndpoint = $this->scopeConfig->getValue(self::XML_PATH_ERP_API_ENDPOINT, $storeScope);
            $apiKey = $this->scopeConfig->getValue(self::XML_PATH_ERP_API_KEY, $storeScope);

            $this->curl->addHeader("Authorization", "Bearer " . $apiKey);
            $this->curl->get($apiEndpoint . 'stocks'); // Örnek

            $response = $this->curl->getBody();
            $httpStatusCode = $this->curl->getStatus();

            if ($httpStatusCode != 200) {
                throw new \Exception("ERP API Hatası: " . $httpStatusCode . " - Cevap: " . $response);
            }

            $stocks = $this->jsonSerializer->unserialize($response);

            // $stocks formatı: [ {"sku": "SKU001", "qty": 150}, ... ]
            if (!is_array($stocks) || !isset($stocks[0]['sku'])) {
                $this->logger->warning('ERP Connector: Stok verisi beklenen formatta gelmedi.');
                return;
            }

            $updatedCount = 0;
            foreach ($stocks as $stockData) {
                try {
                    $sku = $stockData['sku'];
                    $qty = (int)$stockData['qty'];

                    $product = $this->productRepository->get($sku);
                    $stockItem = $this->stockRegistry->getStockItem($product->getId());

                    $stockItem->setQty($qty);
                    $stockItem->setIsInStock($qty > 0);
                    $this->stockRegistry->updateStockItemBySku($sku, $stockItem);
                    $updatedCount++;
                } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
                    $this->logger->warning('ERP Connector: SKU bulunamadı: ' . $sku);
                } catch (\Exception $e) {
                    $this->logger->error('ERP Connector: SKU güncelleme hatası: ' . $sku . ' - Hata: ' . $e->getMessage());
                }
            }

            $this->logger->info("ERP Connector: Stok Senkronizasyonu Tamamlandı. Güncellenen SKU sayısı: " . $updatedCount);

        } catch (\Exception $e) {
            $this->logger->critical('ERP Connector: Stok senkronizasyonu ana hatası: ' . $e->getMessage());
        }
    }
}

Profesyonel ERP Entegrasyonu: Neden Yılmaz Soft?

Yukarıdaki adımlar, bir entegrasyonun “Merhaba Dünya” seviyesidir. Gerçek bir iş operasyonunda, bu yapı hızla yetersiz kalacaktır. Yılmaz Soft olarak, tam ölçekli ERP entegrasyonları için şunları ele alıyoruz:

  1. Performans ve Toplu İşlemler (Mass Operations): Tek tek save() yerine veritabanına toplu, performans odaklı yazma.
  2. Veri Haritalama (Data Mapping): ERP alanları ile Magento alanlarının doğru eşleştirilmesi.
  3. Hata Yönetimi ve Kuyruk Sistemi: Queue tabanlı yeniden deneme mekanizmaları.
  4. İki Yönlü Senkronizasyon: Magento ⇄ ERP bi-directional senkronizasyonu.
  5. Güvenlik ve Veri Bütünlüğü: Token yönetimi, IP kısıtlamaları ve idempotency.
  6. E-Fatura & E-Arşiv Entegrasyonu: Faturalama süreçlerinin otomatik bağlantısı.

Sonuç

Magento 2 ERP entegrasyonu, büyümek isteyen işletmeler için bir zorunluluktur. Bu rehber başlangıç niteliğindedir; ölçeklenebilir, güvenli ve hatasız bir entegrasyon için profesyonel destek alınması önerilir.

Eğer isterseniz, bu yazının diğer entegrasyon postlarıyla aynı formatta (tam içerik + tüm kod blokları) repo’ya ekleyebilirim. Sonraki adım olarak hangi yazıları dönüştürmemi istersiniz?

Etiketler:

Bu yazıyı paylaşın:

YS

Yılmaz Soft

Yılmaz Soft olarak web geliştirme, mobil uygulama ve dijital pazarlama alanlarında profesyonel çözümler sunuyoruz. Müşterilerimizin dijital dönüşüm süreçlerinde yanlarında olmaktan gurur duyuyoruz.

İletişime Geçin

İlgili Yazılar