S SISL docs PL

Synchronization flows

Product sync (Optima → Magento)

Trigger: cron sisl_optima_sync_products (schedule dropdown, default every 1h) or manually from the Dashboard.

ProductSync.run()
  ├─ license.isValidNow()? ──── NO ──► abort with errors=["LICENSE INVALID"]
  │                              YES
  ▼
  loop over pages, pageSize from config (200):
    SyncClient.get('/v1/products?page=N&pageSize=200&tier=1')
        │
        ▼
    HMAC sign + retry 3x → microservice → SQL:
        SELECT Twr_Kod, Twr_Nazwa, Twr_Jm, Twr_Ean,
               CASE WHEN TwC_Typ=1 THEN TwC_Wartosc END AS PriceNet,
               CASE WHEN TwC_Typ=2 THEN TwC_Wartosc END AS PriceGross
        FROM CDN.Towary t LEFT JOIN CDN.TwrCeny c
            ON c.TwC_TwrID = t.Twr_GIDNumer AND c.TwC_TwCNumer = @tier
        WHERE t.Twr_GIDTyp = 16
        OFFSET @offset ROWS FETCH NEXT @pageSize ROWS ONLY
        │
        ▼
    for each SKU:
      ├─ SKU prefix filter (optional)
      ├─ skip if it's a service (jm=GODZ, twr_typ != 0)
      ├─ skip if attribute sisl_optima_sync_disabled = 1
      ▼
      upsert into catalog_product_entity:
        - new → SKU + name + status (config) + visibility (config) + ean + jm + price (if sync_prices ON, otherwise 0)
        - existing → only name + ean + jm (price only if sync_prices ON)
        - description: never overwrites existing

Result: {"created": N, "updated": M, "skipped": K, "errors": [...]} saved in core_config_data (SyncState) and shown in the Dashboard.

Stock sync (Optima → Magento)

Trigger: cron sisl_optima_sync_stock (every 15 min by default) or manually.

StockSync.run()
  └─ license gate ── as above
  ▼
  SyncClient.get('/v1/stocks?warehouse=MAGAZYN&page=N&pageSize=500')
      │
      ▼
  microservice → SQL (real stock from PZ documents):
      SELECT t.Twr_Kod AS Sku, m.Mag_Symbol AS Warehouse,
             SUM(z.TwZ_Ilosc - z.TwZ_Quan) AS Available,
             MAX(i.TwI_Rezerwacje) AS Reserved,
             MAX(i.TwI_Zamowienia) AS OnOrder
      FROM CDN.Towary t
      JOIN CDN.Magazyny m ON m.Mag_Symbol = @warehouse
      LEFT JOIN CDN.TwrZasoby z
          ON z.TwZ_TwrId = t.Twr_GIDNumer AND z.TwZ_MagId = m.Mag_MagId
          AND z.TwZ_TrSIdDost > 0    -- ONLY real documents, ignores synthetic entries
      LEFT JOIN CDN.TwrIlosci i ON i.TwI_TwrId = t.Twr_GIDNumer
      GROUP BY t.Twr_Kod, m.Mag_Symbol
      ▼
  for each SKU:
    rawQty   = Available from Optima
    effective = max(0, rawQty - stock_buffer)
    inStock  = effective > oos_threshold
    StockRegistry.updateStockItemBySku(sku, {qty: effective, isInStock: inStock})

Source of truth: CDN.TwrZasoby filtered by TwZ_TrSIdDost > 0 (i.e., only stock backed by real PZ/PW/MM documents). This is exactly the same logic the Optima GUI uses in the "Stan handlowy" view.

Order forward (Magento → Optima)

Trigger: Magento event sales_order_place_after (observer in events.xml).

OrderPlaceAfter.execute(observer)
  └─ license gate
  └─ orders_enabled config gate
  ▼
  OrderForward.send(order):
      builds payload:
        {
          magentoOrderId, incrementId, createdAt, currency,
          grandTotal, subtotal, tax, shippingTotal, shippingMethod, paymentMethod,
          customer: {email, firstname, lastname, taxId, isCompany, magentoId},
          billingAddress: {...},
          shippingAddress: {...},
          items: [{sku, name, qty, priceNet, priceGross, taxAmount, taxPercent}, ...]
        }
      ▼
      SyncClient.post('/v1/orders', payload)
          ▼
      microservice:
          - validates payload
          - checks license + HMAC
          - generates XML matching Optima Import (TraNag + TraElem + KntKarty)
          - drops it into the folder watched by Optima (XML import in)
          - returns {optimaInvoiceId} after async processing

Fail-soft: if sending fails (microservice offline, license invalid, etc.) — the order stays in Magento with no trace in Optima. The plugin logs a warning + writes a comment in the order status history. Cron retry can be added in v0.2.

License check

Trigger: cron sisl_optima_check_license (daily 03:00 by default).

LicenseChecker.checkNow()
  builds request:
    {
      key: <license key from config>,
      product: "sisl_optima",
      host: <BASE_URL host>,
      version: "0.1.0",
      nonce: <16 bytes random hex>,
      manifest_hash: <SHA256 of 8 critical files>
    }
    ▼
  POST https://license.sisl.pl/check
    ▼
  SISL server:
    - looks up license
    - checks status (active / revoked / expired)
    - checks valid_until
    - checks host binding (first time: bind; later: must match)
    - records manifest hash + compares to previous (logs drift)
    - signs Ed25519 response with echoed nonce
    ▼
  plugin:
    - VERIFIES Ed25519 SIGNATURE (WITHOUT THIS, STATE IS NOT SAVED)
    - checks nonce match (anti-replay)
    - saves state to core_config_data (sisl_optima/license/state):
        {valid, message, valid_until, checked_at, verified: true, was_valid}

Product lifecycle

Action in Optima What happens in Magento
You add a new SKU in the pricelist Next ProductSync creates it in Magento (status=Enabled, price=0 if prices_enabled=No)
You change the price in pricelist 1 StockSync DOES NOT touch it; ProductSync overwrites only if prices_enabled=Yes
You enter a PZ for 50 pcs Next StockSync sets qty=50, in_stock=1
You issue an FA (sale) for 5 pcs TwrZasoby decrements; next StockSync sets qty=45
You mark the item inactive The plugin still syncs it (Twr_NieAktywny is not checked in v0.1 → todo for v0.2)
You delete the item It stays in Magento (orphan); delete manually or wait for v0.2

Next: Diagnostics and errors →