Case study: synchronizacja produktów i cen między Comarch Optimą a panelem B2B
To nie jest demo. To nie jest proof of concept. To jest wdrożenie produkcyjne, które działa na żywych danych firmy handlowej — codziennie, od zamówień po faktury.
Kapelanczyk to firma produkcyjno-handlowa. Panel B2B rozwijany od lat, Comarch ERP Optima jako system magazynowo-księgowy. Dwa odrębne systemy, dwie bazy danych, jedno wymaganie: synchronizacja danych — produkty i ceny muszą się zgadzać po obu stronach.
Ten artykuł opisuje, jak Optima API i VerSync realizują tę synchronizację — z konkretnymi konfiguracjami, logiką cen i mechanizmami bezpieczeństwa. Każdy element, który tu widzisz, jest dokładnie tym samym kodem, który dostają klienci WebArm.
Architektura rozwiązania opiera się na dwukierunkowej wymianie danych pomiędzy systemami Comarch ERP Optima i panelem B2B. Synchronizacja dwukierunkowa zapewnia, że zmiany wprowadzone w jednym systemie są automatycznie widoczne w drugim. VerSync wykorzystuje synchronizację inkrementalną — przesyła jedynie te dane, które uległy zmianie, co przyspiesza proces i ogranicza obciążenie bazy. Każda operacja synchronizacji tworzy kronikę zmian (dziennik zmian), pozwalającą na precyzyjne odtworzenie stanu danych i przywracanie poprzednich wersji. Automatyczne tworzenie kopii zapasowych danych odbywa się przy każdym cyklu synchronizacji, a historia wersji umożliwia rollback do dowolnego punktu w czasie.
Architektura
Comarch ERP Optima (MSSQL)
↓ odczyt SQL / zapis COM
Optima API (REST)
↕
VerSync
↕
Panel B2B (MariaDB)
Pipeline kapelanczykPHP-optimaKapelanczykRM łączy dwa connectory:
connectors:
kapelanczykPHP:
type: kapelanczyk
table: product
primary_key: id
cursor_column: time_stamp
batch_size: 100
optimaKapelanczykRM:
type: optima
base_url: "http://127.0.0.1:5000"
firma: "Kapelanczyk RM"
auth:
type: api_key
header: X-Api-Key
token: "${OPTIMA_RM_API_KEY}"
timeout: 15m
rate_limit:
requests_per_second: 3
burst_size: 20
entity_mappings:
- entity_type: product
poll_interval: 30s
init_batch_cooldown: 3s
conflict_resolution:
strategy: lww
source_priority:
optimaKapelanczykRM: 1
kapelanczykPHP: 2
kapelanczykPHP— connector do bazy MariaDB panelu B2B, kursor potime_stamp, batch po 100 rekordówoptimaKapelanczykRM— connector REST do Optima API z rate limitem 3 req/s, timeout 15 minut na operację COM
Source of truth to panel B2B (kapelanczykPHP). W przypadku konfliktu — np. jednoczesnej edycji tego samego produktu po obu stronach — wygrywa wersja z panelu. Conflict resolution: LWW (Last Write Wins) z priorytetem: Optima = 1, PHP = 2 (wyższy numer = wyższy priorytet).
Tworzenie nowych rekordów jest jednokierunkowe. W sekcji filters connector PHP ma create_policy: deny (nie tworzy nowych rekordów z Optimy), a Optima ma create_policy: allow — nowe produkty mogą powstawać w panelu B2B i trafiać do Optimy, ale nie odwrotnie.
Wyzwanie: produkty się nie zgadzają
Pozornie prosta synchronizacja produktów okazuje się złożona, gdy przyjrzysz się danym po obu stronach.
Częściowe pokrycie. Optima zawiera materiały, produkty gotowe, usługi, opakowania. Panel B2B potrzebuje tylko produktów handlowych. Nie da się zsynchronizować „wszystkiego” — potrzebny jest precyzyjny filtr.
Filtrowanie w pipeline definiujesz jako klauzulę where po stronie source connectora:
filters:
connectors:
kapelanczykPHP:
where: "brand_id = 2 AND childmodel_type IN (0, 3) AND status > -3"
create_policy: deny
optimaKapelanczykRM:
create_policy: allow
brand_id = 2— produkty marki Versanischildmodel_type IN (0, 3)— produkty proste i zestawy, bez wariantów i wirtualnychstatus > -3— wyklucza usunięte
VerSync stosuje where po stronie source connectora, więc do synchronizacji trafiają tylko rekordy spełniające wszystkie kryteria.
Rozbieżność kodów. Historycznie pole kod w Optimie nie odpowiada polu model w panelu. Match key łączy model (PHP) z polem kod (Optima). Dodatkowa komplikacja: Optima przechowuje kody wielkimi literami, panel — nie.
match_key: model
source_of_truth: kapelanczykPHP
fields:
- canonical: model
connectors:
kapelanczykPHP:
column: model
optimaKapelanczykRM:
field: kod
writable: true
sync: bidirectional
role: match_key
transform: to_upper
- canonical: model_to_numerkat
connectors:
kapelanczykPHP:
column: model
optimaKapelanczykRM:
field: numerKat
writable: true
sync: kapelanczykPHP->optimaKapelanczykRM
role: copy
transform: to_upper
Transform to_upper normalizuje obie wartości przed porównaniem. Pole model jest matchowane z kod w Optimie i dodatkowo kopiowane jednokierunkowo do numerKat — to osobne pole w Optimie (numer katalogowy), które musi być spójne z kodem panelu.
Trzy cenniki, trzy strategie
Synchronizacja cen to najciekawsza część tego pipeline’u, ponieważ obejmuje automatyczną synchronizację i aktualizowanie danych cenowych w czasie rzeczywistym pomiędzy Comarch ERP Optima a panelem B2B. Pipeline używa trzech cenników, każdy z inną logiką synchronizacji.
| Cennik | Init | Watch | Logika |
|---|---|---|---|
| Zakupu | Optima → PHP | Optima → PHP | optima_price_netto |
| Hurtowa | PHP → Optima | Dwukierunkowa | optima_price_netto |
| Detaliczna | PHP → Optima | Dwukierunkowa | optima_price_brutto_stawka |
Jak to działa? Pole sync kontroluje kierunek w trybie watch (i domyślnie w init). Pole initial_sync_direction nadpisuje kierunek wyłącznie dla init. Gdy initial_sync_direction jest pominięte — init używa tego samego kierunku co watch.
Konfiguracja cenników w product.yaml:
prices:
- canonical: purchase_price
precision: 2
kapelanczykPHP:
source: product_price
product_list_id: 0
optimaKapelanczykRM:
cennik_numer: 1
logic: optima_price_netto
cennik_nazwa: zakupu
waluta: PLN
sync: optimaKapelanczykRM->kapelanczykPHP
- canonical: wholesale_price
precision: 2
kapelanczykPHP:
source: product_price
product_list_id: 1
optimaKapelanczykRM:
cennik_numer: 2
logic: optima_price_netto
cennik_nazwa: hurtowa
waluta: PLN
initial_sync_direction: kapelanczykPHP->optimaKapelanczykRM
sync: bidirectional
- canonical: retail_price
precision: 2
kapelanczykPHP:
source: product.suggested_retail_price
optimaKapelanczykRM:
cennik_numer: 5
logic: optima_price_brutto_stawka
cennik_nazwa: detaliczna
waluta: PLN
initial_sync_direction: kapelanczykPHP->optimaKapelanczykRM
sync: bidirectional
Cennik zakupu (purchase_price)
Ceny zakupu istnieją tylko w Optimie (cennik nr 1) — to ceny dostawców, wprowadzane ręcznie lub z dokumentów PZ. Pipeline pobiera je do panelu B2B (product_list_id: 0) jako informację referencyjną. Kierunek jednokierunkowy: sync: optimaKapelanczykRM->kapelanczykPHP — ten sam w init i watch, bo nie ma initial_sync_direction.
Cennik hurtowy (wholesale_price)
Cennik hurtowy (cennik nr 2 w Optimie, product_list_id: 1 w panelu) to główny cennik sprzedażowy. Watch (sync: bidirectional) działa dwukierunkowo. Init ma nadpisany kierunek: initial_sync_direction: kapelanczykPHP->optimaKapelanczykRM — przy pierwszej synchronizacji ceny hurtowe lecą z panelu do Optimy (wyrównanie początkowe).
Cennik detaliczny (retail_price)
Cennik detaliczny (cennik nr 5 w Optimie, suggested_retail_price w panelu) to ceny brutto. Ten sam pattern co hurtowy: sync: bidirectional + initial_sync_direction: kapelanczykPHP->optimaKapelanczykRM. Tu pojawia się konwersja VAT — logika optima_price_brutto_stawka automatycznie przelicza brutto/netto.
Reguły przeliczeniowe z prices.yaml:
price_rules:
optima_price_netto:
read: wartoscNetto
to_internal: input
write: wartoscNetto
to_external: input
optima_price_brutto_stawka:
read: wartoscBrutto
to_internal: round(input / (1 + vat/100), 2)
write: wartoscBrutto
to_external: round(input * (1 + vat/100), 2)
vars:
vat:
from: record
key: stawka
optima_price_netto to pass-through — wartość netto bez konwersji. optima_price_brutto_stawka przelicza brutto na netto (i odwrotnie) ze stawką VAT z pola stawka karty towaru.
Dwufazowa automatyczna synchronizacja
Automatyzacja procesów synchronizacji produktów jest podzielona na dwie fazy, podobnie jak w typowej integracji sklepu internetowego z Comarch ERP Optima:
Faza 1: brand Versanis — produkty katalogowe, dostępne w ofercie publicznej. Mniejszy zbiór, stabilniejsze dane, rzadsze zmiany. Ta faza działa jako pierwsza, żeby ustabilizować bazowy katalog.
Faza 2: brand Kapelanczyk — produkty klientów, zamówienia specjalne, konfiguracje indywidualne. Większy zbiór, częstsze zmiany, wyższe ryzyko konfliktów.
Rozdzielenie ma sens operacyjny: jeśli faza 2 napotka problem (np. nowy typ produktu, nieoczekiwany format danych), faza 1 działa niezależnie. Izolacja awarii.
Wydajność: odczyt z SQL, zapis przez COM
Comarch ERP Optima nie ma oficjalnego REST API. Standardowy dostęp do danych to COM — technologia z lat 90., jednowątkowa, wrażliwa na timeout i wycieki pamięci. Optima API od WebArm rozwiązuje ten problem architekturą dwuwarstwową (szczegółowe porównanie COM vs REST).
Odczyt: bezpośrednio z MSSQL
Endpoint /api/sync/towary omija COM całkowicie i jest kluczowy dla automatycznej synchronizacji stanów magazynowych z Comarch ERP Optima. Czyta dane towarów bezpośrednio z bazy SQL Server Optimy, gdzie dane są przechowywane, z cursor-based pagination. Konfiguracja tego endpointu w pipeline:
endpoints:
fetch_changes:
method: GET
path: "/api/sync/towary"
query_params:
modifiedSince: "{cursor}"
pagination:
mode: cursor
page_size: 100
has_more_path: hasMore
next_cursor_path: nextCursor
VerSync odpytuje GET /api/sync/towary?modifiedSince={cursor}&take=100 — dostaje tylko towary zmienione od ostatniego cyklu, po 100 rekordów na stronę.
Batch read dla wielu towarów jednocześnie:
endpoints:
fetch_by_match_keys:
method: POST
path: "/api/towary/batch/read"
request:
keys_field: kody
key_type: string
POST /api/towary/batch/read
Content-Type: application/json
{
"kody": ["PROD-001", "PROD-002", "PROD-003"]
}
Oba endpointy korzystają z SQL — szybki odczyt, brak ograniczeń COM, pełna wydajność bazy danych.
Zapis: zawsze przez COM
Zapisy do Optimy muszą przechodzić przez COM. To nie jest wybór architektoniczny — to wymóg Comarch. COM waliduje dane biznesowe: sprawdza stawki VAT, oblicza wartości, aktualizuje powiązane tabele. Bezpośredni zapis do SQL pominąłby tę walidację i mógłby uszkodzić bazę.
Mechanizmy bezpieczeństwa Optima API
COM jest niestabilny z natury. Optima API ma wbudowane zabezpieczenia, konfigurowane w appsettings.json, które zapewniają że dane są chronione, a nad bezpieczeństwem systemu czuwają dedykowane mechanizmy:
{
"ComQueueCapacity": 64,
"MemoryGuardPrivateMb": 1200,
"MemoryGuardRecycleAfterSeconds": 120,
"ComHangWatchdogSeconds": 180,
"ComHangWatchdogPollSeconds": 5,
"OverloadShieldEnabled": true,
"OverloadBlockThreshold": 8
}
- MemoryGuard — próg
MemoryGuardPrivateMb: 1200. Gdy proces COM przekroczy 1200 MB, sesja jest recyklowana zanim dojdzie do crash. Recycle po 120 sekundach stabilnej pracy - ComHangWatchdog — timeout
ComHangWatchdogSeconds: 180. Jeśli operacja COM się zawiesi, watchdog ją przerywa co 5 sekund sprawdzając stan - OverloadShield — middleware blokujący żądania gdy aktywnych operacji >
OverloadBlockThreshold: 8 - Bounded channel queue —
ComQueueCapacity: 64, single STA thread. Kolejka operacji COM z ograniczoną pojemnością
Mechanizmy bezpieczeństwa VerSync
Po stronie VerSync:
- In-memory channel queue z backpressure — gdy kolejka jest pełna, nowe operacje dostają błąd natychmiast (fail-fast), zamiast czekać w nieskończoność
- Init batch — batch po 100 rekordów, cooldown 3 sekundy między batchami (
init_batch_cooldown: 3s). Chroni bazę docelową przed zalaniem - Outbox relay — guaranteed delivery. Każda operacja zapisu trafia najpierw do outboxa. Dopiero po potwierdzeniu dostarczenia jest usuwana
- MemoryPressureChecker — VerSync odpytuje guard state Optima API i wstrzymuje operacje, gdy API raportuje memory pressure
- Kopie zapasowe danych — VerSync przechowuje historię synchronizacji i snapshoty, co umożliwia przywracanie wielu wersji danych i planowe tworzenie kopii zapasowych niezależnie od głównego procesu synchronizacji
Bezpieczeństwo operacyjne i kopii zapasowych
Synchronizacja danych produkcyjnych wymaga mechanizmów bezpieczeństwa na każdym etapie. Automatyczne tworzenie kopii zapasowych danych przy każdym cyklu synchronizacji zapewnia ochronę i możliwość przywracania informacji w razie niepowodzenia operacji.
Dry-run
Przed każdą nową konfiguracją — dry-run. Flaga --dry-run uruchamia pełny cykl synchronizacji bez zapisywania zmian:
versync init --pipeline kapelanczykPHP-optimaKapelanczykRM --dry-run
VerSync porównuje oba systemy i generuje raport z polami ReconcileSummary:
{
"entity_type": "product",
"left_source": "kapelanczykPHP",
"right_source": "optimaKapelanczykRM",
"summary": {
"total_left": 847,
"total_right": 912,
"matched": 835,
"identical": 801,
"divergent": 34,
"only_left": 12,
"only_right": 77
}
}
matched — rekordy z pasującym match key po obu stronach. divergent — matchowane, ale z różnicami w polach. only_left / only_right — rekordy bez odpowiednika po drugiej stronie. Zero niespodzianek.
Single-record test
Flaga --record filtruje synchronizację do jednego rekordu po match key:
versync init --pipeline kapelanczykPHP-optimaKapelanczykRM --record "PROD-001" --dry-run
Rollback i przywracanie
Każda operacja synchronizacji tworzy snapshot, który może być wykorzystany do przywracania danych w przypadku awarii lub utraty informacji. W razie problemu — szybkie przywracanie do stanu sprzed konkretnego cyklu, zarówno z lokalnych kopii zapasowych, jak i z historii wersji w bazie VerSync.
Monitoring
Metryki synchronizacji dostępne w Grafanie (dodatkowe case studies i przewodniki znajdziesz na blogu o integracji z Comarch ERP Optima):
- Liczba zsynchronizowanych rekordów per cykl
- Czas trwania cyklu (odczyt + zapis)
- Błędy z pełnym kontekstem (jaki rekord, jaki błąd, kiedy)
- Zużycie pamięci COM i VerSync
Podsumowanie
Synchronizacja produktów i cen między Comarch Optimą a panelem B2B działa na produkcji — z trzema cennikami, dwufazowym pipeline’em i mechanizmami bezpieczeństwa na każdym poziomie.
To dokładnie ten sam kod i ta sama architektura, którą dostają klienci WebArm. Żadnych demo, żadnych uproszczonych wersji — pełny produkt, przetestowany na żywych danych.
Chcesz zsynchronizować produkty lub ceny z Optimy ze swoim systemem? Sprawdź odpowiedzi na najczęstsze pytania lub skontaktuj się z nami.
Robert Mońka
Programista, właściciel firmy produkcyjno-handlowej, lider zespołu WebArm. Wraz z zespołem rozwija Optima API i VerSync — REST API i synchronizatory do Comarch ERP Optima używane też w jego własnych firmach.
LinkedIn →Masz pytania o integrację z Comarch Optima?