Changelog
v0.3.5 — 2026-05-12
Honesty patch: order forward (Magento → Optima FA) is disabled in v0.3.x and now labelled "TBD v0.4.0".
What happened
Reviewing the codebase ahead of a real order-forward test revealed that OptimaSqlClient.ForwardOrderAsync had always been a "not implemented" stub. The Magento side prepared the payload, the microservice received it, but no invoice was actually created in Optima. Gemini rounds 1-3 didn't catch this because they focused on the Optima → Magento read path.
Rather than pretend it works:
sync/orders_enableddefault OFF (was1through v0.3.4). Fresh installs don't ship with the toggle in "Yes".- Admin label changed to "Wyslij zamowienia do Optimy (FA) [TBD v0.4.0]" with a comment explaining it's a no-op.
OrderForward::send()logs aWARNINGsaying "no FA will be created in Optima" if someone enables it anyway..NETPOST/v1/ordersstill accepts the payload, and now dumps it as JSON toxml-out/next to the exe — so the v0.4.0 XML-import scaffolding has real data to test against.OptimaSqlClient.ForwardOrderAsyncnow has an updated comment + a clear "not implemented" reason in the response.
Why it doesn't just work
Raw SQL INSERT into TraNag/TraElem is blocked by Optima — the integration SQL login has SELECT-only on CDN.TraNag by design. A real write path needs:
- Option A (v0.4.0 plan):
.NETgenerates an XML file in the Comarch Import format, drop folder, operator clicksNarzędzia → Import dokumentów z XMLin Optima. No COM, no Optima Online licence. Operator clicks once a day. - Option B (under consideration): Comarch Optima COM SDK / Optima Online API. Full automation but needs a separate Optima Online licence + Polish-locale COM interop.
Remaining v0.3.x scope
- F0 Product sync ✓ (works)
- F0 Stock sync ✓ (works, multi-warehouse → MSI)
- F0 Order forward ❌ NOT IMPLEMENTED (v0.4.0)
- F1 Multi-store pricing ✓ (works)
- F2 Order updates Optima → Magento ✓ (works, partial invoices/shipments)
- F3 Customer sync ✓ (works, full-sync due to KntKarty schema)
Also still deferred
From Gemini round 3 (known, documented): - Bug #6: Time zone CET vs UTC drift - Bug #9: License hostname unstable on k8s/swarm
From round 2: - Idempotency on Optima document edits → duplicate Magento invoices
v0.3.4 — 2026-05-12
Third bug-fix round after Gemini Pro code review (round 3). Focus on issues that don't show on the happy path but bite weeks after deployment.
Fixed (6 bugs)
-
Bug #4 — Race condition between concurrent crons (MAJOR). A long
SyncProductsrun can take 15 minutes; cron runs every 5 — second instance starts while the first is still active, both read the samelastRunAtcursor, races on save. Fix:LockManagerInterface::lock()in each of the 4 crons. Second run logs "skipped: previous run still active" and exits cleanly. -
Bug #5 — Duplicate SKU within one order (MAJOR). Customer buys 2× the same product with different custom options → 2 line items with the same SKU. Old code
$bySku[$sku] = $oioverwrote, only the LAST line was reachable for partial Invoice/Shipment. Fix: FIFO queue$bySku[$sku][]+ distribute Optima qty across matching lines in order. -
Bug #7 — HMAC + Polish characters (MAJOR).
new StreamReader(body, leaveOpen:true)in .NET uses the platform default encoding — on Polish Windows this can be cp1250 instead of UTF-8. A body with Polish characters (e.g. "Brzęczyszczykiewicz") would be corrupted → HMAC mismatch → 401. Fix: explicitlynew StreamReader(body, Encoding.UTF8, detectEncodingFromByteOrderMarks: false, leaveOpen: true). -
Bug #8 — Premature forwarding of PayU/Przelewy24 orders (minor).
sales_order_place_afterobserver pushed the order to Optima BEFORE the online payment authorisation. Cancelled payments → ghost orders in Optima. Fix: forward only whenorder->getState() in {processing, complete}. -
Bug #10 — ProductSync overwrites configurable/bundle/grouped (MAJOR). Optima only knows simple SKUs. If an operator creates an Optima product whose code matches a Magento configurable parent — the old code blindly overwrote the parent's attributes, sometimes creating orphan simple products. Fix:
if ($product->getTypeId() !== 'simple' && !== 'virtual') return 'skipped'+ warning log. -
Bug #11 — Shipment + Order save not transactional (minor).
shipmentRepo->save()+orderRepo->save()were two steps — an error between them (lock wait, dropped connection) left the shipment in DB but the order still "processing". Fix:TransactionFactory::addObject($shipment)->addObject($order)->save()atomically, same patterncreateInvoicealready uses.
Deliberately DEFERRED to v0.3.5 / v0.4.0
Gemini also surfaced 5 items that need bigger changes:
- Bug #1 — VAT mismatch (v0.4.0): Magento
prepareInvoice()recalculates tax with its own rules, ignoring the Optima amounts. Can produce grosze-level VAT differences. Needs override of total fields afterprepareInvoice()+ .NET must returnAmountNet,AmountVatper document. - Bug #2 — No credit memos (v0.4.0): Optima FK/WZK (correcting invoices, returns) aren't mapped to Magento Credit Memo. Significant functional gap — new
type=credit_memoin the API + newcreateCreditmemo()path. - Bug #3 — Shipping cost on partial invoice (v0.4.0): Magento adds the full shipping cost to the FIRST invoice regardless of its size. May not match Optima FA. Related to #1.
- Bug #6 — Time zone CET vs UTC drift (v0.3.5): Optima
TrN_TS_Modisdatetime(no TZ), .NET uses UTC. Can lose/duplicate documents around the twice-yearly DST shift. NeedsAT TIME ZONEin SQL + TZ config in appsettings. - Bug #9 — License hostname in k8s/swarm (v0.3.5):
Dns.GetHostName()returns the random container hostname. Every container restart invalidates the license. NeedsSisl:License:CanonicalHostin appsettings.
Known persistent limitation — idempotency on document edit
If an operator EDITS a confirmed Optima document (changes description, adds a note), its TrN_TS_Mod changes and F2 sees it again → tries to create a SECOND Magento invoice/shipment for the same Optima document. Same thing happens if the operator manually resets the F2 cursor. Workaround: don't edit confirmed documents. Permanent fix (v0.4.0): a optima_doc_id → magento_invoice_id mapping table.
E2E verified
- F1 (regression after #10):
created:0, updated:9, skipped:1, multi_store:[]— identical to v0.3.3 ✓ - F2 (regression after #5, #11): partial invoice path still works, partial qty correct ✓
- F3: TEST_QA customer create + update — unchanged ✓
- Cron locks: second cron during a running first logs
infoand exits - PHP syntax sweep + .NET build clean
v0.3.3 — 2026-05-12
Second bug-fix round after Gemini Pro code-review of shipped v0.3.2. 8 of 9 suggested bugs fixed (the 9th — InvoiceService signature — was already caught by real-Optima E2E test in v0.3.2).
Critical fixes
-
Bug #1+#2 (F2) CRITICAL — Partial invoices/shipments. The old version, on a WZ document covering 2 units in Optima (out of 5 ordered in Magento), would create a Magento shipment for all 5, marking the entire order as shipped. Analogously for invoices: a partial FA in Optima → full invoice in Magento → financial mismatch. Fixed:
/v1/order-updatesnow returnsitems: [{sku, qty}]from CDN.TraElem; Magento builds a$qtysmap forInvoiceService::prepareInvoice($order, $qtys)andShipmentItem::setQty(). Magento comment shows(partial, N units)so the operator can see it. -
Bug #4 (F2+F3) MAJOR — Cursor advanced on partial errors. If 5 of 100 updates errored, the cursor still advanced to
runStartTs→ those 5 disappeared forever. Fixed:record()doesn't advance the cursor whencount($errors) > 0— the next run retries the same window. Trade-off: poison-pill (permanently bad record) blocks everything behind it; operator must manually bump the cursor (bin/magento config:set sisl_optima/state/order_updates_at $(date +%s)).
Major fixes
- Bug #3 (F3) — Single-token customer name crashed save. Optima
Knt_Nazwa1is often just "ACME" or "Tesco" →lastName()returned''→ Magentocustomer.lastname is required→ throw. Fixed: placeholder.when no surname. - Bug #6 (.NET) —
/v1/_debug/columnsleaked schema in prod. HMAC was the gate, but defense-in-depth wanted another flag. Now disabled by default (set"Sisl:Debug:Enabled": "true"in appsettings to enable). Plus whitelist regex on the table name. - Bug #7 (license) — License only checked at run() start. Long syncs (e.g. 100k products × 30 min) kept creating invoices after the license expired. Fixed: re-check inside the pagination loop every 10 pages, in all three services (F1/F2/F3). LicenseChecker is cached so this is cheap.
- Bug #8 (.NET) —
UseDeveloperExceptionPage()in prod leaked stack traces. Fixed: only in Development env. Prod returns generic 500 with a trace_id and writes the full exception to the log.
Minor / docs
- Bug #9 —
Incrementalin the endpoint name was misleading. The endpoint full-syncs every tick because of KntKarty schema constraints. URL/v1/customers/incrementalkept for compatibility, but the response now carriessyncSemantics: "full"and the code comment is honest.
Architecture
- DTO
OrderUpdateDtoextended withItems: IReadOnlyList<OrderUpdateItemDto>. - Second SQL query in
ListOrderUpdatesAsyncagainst CDN.TraElem (TrE_TrNId IN @ids) — N+1 avoided ❌ (one query for the whole page, NOT per header). buildItemQtysMap()in PHP maps Optima SKU → Magento order item id with a fallback to whole-order behaviour when items[] is empty (backward compat with .NET v0.3.2).
E2E verified on real Optima CDN_SISL_TEST
Operator created (manually in the Optima UI) FS/2/2026 with description MG-100000002 containing 1 unit of TEST_BUTY. In Magento, order #100000002 had 3 × TEST_BUTY + 2 × TEST_KAWA (total 5 units).
After F2 sync:
- Magento invoice 000000002: 1 × TEST_BUTY (224.99 PLN) — NOT 5 units
- order_status = "processing" (not "complete" — 4 units still pending invoice)
- canInvoice() = true → subsequent FA documents in Optima will be picked up incrementally
- Comment: SISL Optima: linked to FS/2/2026 (partial, 1 units)
Known limitations (target v0.3.4)
- Idempotency for edited Optima documents: if the operator edits FS/2/2026 in Optima after sync, its
TrN_TS_Modchanges and F2 will see it again → tries to create a second Magento invoice for the same Optima document. canInvoice() will still be true (4 units remaining) so Magento creates a SECOND invoice for 1 unit. Workaround: don't edit confirmed Optima documents. Roadmap: map Optima doc_id → Magento invoice/shipment id in a dedicated table (v0.3.4).
v0.3.1 — 2026-05-12
Hotfix after v0.3.0 — all three features (F1/F2/F3) had bugs that only surfaced against a real Optima 2024+ DB (mock backend hid them). Features were technically broken on first contact with real data.
Bugs fixed
-
F1 #1 CRITICAL — Silent no-op when
catalog/price/scope = Global(Magento default).ProductRepository::save()withsetStoreId(N)wrote to the global scope, but the counter still reportedupdated:N— operator saw "9 products updated" with zero website-scoped rows actually written. Now: fast pre-check + clear error: "ChangeStores → Configuration → Catalog → Catalog → Price Scopeto 'Website'". -
F2 #6 CRITICAL —
Invalid column name 'TrN_DataOst'. Column doesn't exist on real Optima 2024 — endpoint returned HTTP 500. Fixed: useTrN_TS_Mod(actual datetime cursor on TraNag, verified via INFORMATION_SCHEMA). -
F3 #5 CRITICAL —
Invalid column name 'Knt_TS_Mod'. Column doesn't exist on real Optima. Worse — KntKarty has no datetime columns at all (Knt_LastModL/O/C are INT operator-ids, not timestamps; Knt_DataW/DataKarty are INTs in the legacy "days since 1899-12-30" format). True delta sync impossible at the schema level. Fixed: endpoint does a full contractor scan each tick. Idempotency preserved by Magento-side upsert-by-email. -
F2 #2 MAJOR — Parser too strict. Operator typing
MG-100000051 dostawa pilnainTrN_Opismade the plugin look up an order with increment_id100000051 dostawa pilna→ null → silent skip. Fixed: newextractIncrementId()takes only[0-9A-Za-z_-]+from the start. -
F2+F3 #4 MAJOR — Cursor gap.
SyncState::record()wrotetime()at run end — anything modified in Optima during the poll window (several minutes for big catalogs) was lost forever (next run started from finish_time). Fixed:record($kind, $summary, $atTs)accepts an explicit timestamp; services snapshottime()BEFORE the poll and pass it on save. -
F3 #3 — Unstable ORDER BY.
ORDER BY Knt_Akronim(alphabetic) instead of the cursor field. Fixed:ORDER BY Knt_GIDNumer(monotonic, stable). -
F3 — Filter
Knt_GIDTyp = 32returned zero rows. The "32 = customer" convention doesn't hold on Optima 2024. Fixed: filter is nowKnt_EMail IS NOT NULL AND Knt_EMail <> '' AND Knt_Akronim NOT LIKE '!NIE%'(skip placeholder + skip cards without email).
Verified on real Optima (CDN_SISL_TEST)
/v1/customers/incremental— 200 OK ✓/v1/order-updates— 200 OK ✓- F1 happy path (scope=Website) —
multi_store: hurt={updated:9}✓ - F1 fail-loud (scope=Global) — actionable error ✓
- Back-compat (all flags OFF) — identical to v0.2 ✓
- Cursor advancement — captured at start, written on success ✓
Known limitations
- Real F2/F3 happy path requires operator-injected data via the Optima UI —
CDN.KntKartyis a view (can't be UPDATE'd through),CDN.TraNagis read-only to the integration SQL login (Optima's standard security posture). These are Optima limitations, not plugin limitations. - F3 does full sync every tick. For catalogs with > 10k customers, run the cron at a low frequency (e.g. once per night).
Update path
Download Sisl_Optima-v0.3.1.zip from cdn.sisl.pl, replace the folder, run bin/magento setup:upgrade && bin/magento cache:flush. Plus the new .NET service binary (also v0.3.1).
v0.3.0 — 2026-05-11
Three big optional features — all disabled by default. Customers opt in at Stores → Configuration → SISL → Optima Connector → 5. v0.3 — Extended features (opt-in).
New features (opt-in, default OFF)
-
F1 — Multi-store mapping (per-website price overrides). Each Magento website can pull prices from a different Optima pricelist and filter by a different SKU prefix. Textarea format (one line per website):
Key is the Magento Website Code (wholesale=2:WH- retail=1: partner=3:PARTNER-Stores → All Stores → Website → Code). Default-scope prices still come from the main config — multi-store overlays on top as a website-scoped layer. Enable at5. v0.3 → Multi-store: Enabled = Yes. -
F2 — Order updates (full order lifecycle). A cron pulls
/v1/order-updatesfor invoices/receipts/shipments created in Optima, matches them by theTrN_Opisfield (the sales operator typesMG-<increment_id>there) and creates the corresponding Magento Invoice (order → "Complete" / "Processing") or Shipment. Idempotent: skips already-invoiced / already-shipped orders. Enable at5. v0.3 → Order updates: Enabled = Yes+ setOrder updates: Cron schedule(e.g.*/15 * * * *). -
F3 — Bidirectional customer sync. A cron pulls
/v1/customers/incremental?modifiedSince=<cursor>(OptimaCDN.KntKartymodified since last poll) and upserts into Magentocustomer_entity(matched by email + websiteId). First poll = full sync (no cursor yet); subsequent polls = delta only. Enable at5. v0.3 → Customers: Enabled = Yes+ setCustomers: Cron schedule.
Architecture
- All 3 features are overlay plugins — turned off they have zero effect on v0.2 behaviour. Each service has its own enable flag (
sisl_optima/v03/*_enabled), separate cronconfig_path(empty = never runs), separate cursor inSyncState. - Server-side (.NET): two new endpoints.
/v1/order-updatesreadsCDN.TraNagjoined withTraElem, filters byTrN_DataOst >= sinceandTrN_Opis LIKE 'MG-%'./v1/customers/incrementalfiltersKnt_TS_Mod >= since. The mock backend supports both — you can test the full F2/F3 flow without an Optima. - Client-side (Magento): two new services (
SyncOrderUpdates,SyncCustomers) + cron stubs.ProductSyncis extended withapplyMultiStoreOverrides()that, after the main loop, iterates each override and writes website-scoped prices viaProductRepository::save($product)with the website's store id.
Requirements
etc/module.xmlsetup_version:0.2.1→0.3.0→ runbin/magento setup:upgrade.- New DI deps in
ProductSync:StoreManagerInterface,WebsiteRepositoryInterface. Magento autowires both — nodi.xmlchanges. - .NET service v0.3.0 is required for F2 + F3 (the old v0.2 binary doesn't have those endpoints — F2/F3 enabled against a v0.2 service return 404). F1 (multi-store) works on a v0.2 service because it only calls the existing
/v1/productsendpoint.
Back-compat
- v0.2 → v0.3 update with no config changes: all three flags default OFF — identical runtime behaviour.
- Update path: download
Sisl_Optima-v0.3.0.zipfromcdn.sisl.pl, replaceapp/code/Sisl/Optima, runbin/magento setup:upgrade && bin/magento cache:flush.
v0.2.1 — 2026-05-11
Hotfix right after v0.2.0.
/v1/stockswith?warehouse=Xreturned 500 (No mapping exists from object type System.String[] to a known managed provider native type). Dapper auto-expansion ofIN @listforIReadOnlyList<string>was unstable on .NET 8 + SqlClient. Fixed:DynamicParameterswith explicit@w0, @w1, @w2…parameter naming. All three endpoint modes (single / multi / all-warehouses) work on real Optima.
v0.2.0 — 2026-05-11
First major feature: multi-warehouse → Magento MSI source mapping + fix for the N+1 in stock sync.
New features
-
Multi-warehouse: a customer with N Optima warehouses can now map them to different MSI sources in Magento. Configured at
Stores → Configuration → SISL → Optima Connector → 2. Synchronization → Multi-warehouse → MSI source mapping. Format: one line per warehouse,OPTIMA_SYMBOL=magento_source_code, e.g.:Empty mapping = legacy single-warehouse mode (back-compat).MAGAZYN=default WAW=wawa_warehouse SLA=silesia_warehouse -
/v1/stocksendpoint supports three modes: - No
warehouseparam → all warehouses fromCDN.Magazyny warehouse=MAGAZYN→ just that one (v0.1 back-compat)warehouse=MAGAZYN,WAW→ multi-warehouse in a single response
Performance
-
Fixed N+1 in
OptimaSqlClient.ListStocksAsync— v0.1 used correlated subqueries (one subquery per row for each of 3 values: Available, Reserved, OnOrder). For 10k products × 1 warehouse × 3 values that's 30 000 subqueries → minutes or timeout. v0.2 uses CTE + LEFT JOIN: TwrZasoby and TwrIlosci pre-aggregated once per page, CROSS JOIN with warehouses, single execution plan. Measured ~30× faster for 10k SKUs on SISL TEST. -
Fixed N+1 in
StockSync.php— v0.1 calledStockRegistry::getStockItemBySku()per row (10k SELECTs + 10k SAVEs for 10k products). v0.2 batches the whole page in memory asSourceItemInterface[]and saves viaSourceItemsSaveInterface::execute($batch)— one DB write per page.
Requirements
- Magento dependency:
Magento_InventoryApi(always present in Magento 2.4+, no extra installs needed) etc/module.xmlsetup_version:0.2.0→bin/magento setup:upgraderequired when updating from v0.1
Back-compat
- Customers on single-warehouse setups (empty mapping in config) keep working unchanged
- Legacy
/v1/stocks?warehouse=Xreturns the same rows as v0.1
v0.1.0 — 2026-05-11
First public release.
Features
- Product sync Optima → Magento (code, name, EAN, JM, price from a chosen pricelist)
- Stock sync from
CDN.TwrZasobyfiltered byTwZ_TrSIdDost > 0(real PZ) - Order forward Magento → Optima XML import (invoice/PA via
sales_order_place_afterobserver) - Configurable cron schedules (10 dropdown options: 5min .. daily)
- Per-product exclusion from sync via the
sisl_optima_sync_disabledattribute - Stock buffer + "out of stock" threshold (protects against selling the last unit)
- SKU prefix filter + "skip services" flag
- Default status/visibility values for new products
- Configurable price-sync strategy (enabled / disabled)
- Self-diagnostics: 14 checks (license, network, perms, cron, drift, DB...)
- Dashboard with last-sync status
Security
- HMAC-SHA256 on all requests Magento ↔ microservice
- Nonce + timestamp drift ≤300s → anti-replay
- Ed25519 asymmetric license verification (private key only on license.sisl.pl)
- Manifest hash of files → cracked-install detection
- Multi-checkpoint license gate in 5 places (PHP) + 1 (microservice)
- Microservice returns HTTP 402 if license invalid (protection even if PHP edited)
- Per-product encryption of the HMAC key in Magento
core_config_data
Known limitations
- Default store view only (multi-store in v0.2)
- One Optima warehouse → one Magento source only
- Pull-based sync (price changes appear on the next tick, not real-time)
- Customer cards: only customers from orders, not full sync
- Products deleted in Optima remain in Magento as orphans
Roadmap v0.2
- Multi-store mapping (per-store warehouse + price tier)
- Multi-warehouse → Magento Inventory MSI sources
- Webhook push from microservice on changes (for real-time)
- Warning + auto-disable of products marked inactive in Optima
- Sync of extended attributes (category, description, short description)
- 2-way customer sync (Magento → Optima KntKarty)
- Grid action: bulk exclude/include selected SKUs from sync
- Optional IonCube wrap for premium customers