Engineering · Case Study

How We Built a WooCommerce ↔ Entersoft Sync That Cut Order-Entry Time by 70%

A field-tested architecture for keeping a 65,000-SKU B2B catalogue, three pricing rule chains and a hot-path order pipe in lock-step between WooCommerce and Entersoft Business Suite - and the three things that nearly killed the project.

Μοιραστείτε
How We Built a WooCommerce ↔ Entersoft Sync That Cut Order-Entry Time by 70% - Tec Dynamics

In short

A Greek wholesale distributor was running their B2B operation on phone, fax and email orders, manually re-keyed into Entersoft Business Suite. Eight months after we shipped the WooCommerce ↔ Entersoft middleware that replaced that workflow, internal order-entry time was down 70%, online order volume was up 45%, and zero pricing disputes had been raised. This is the architecture, the three gotchas we hit, and what we would do differently next time. Related: our Entersoft integration service page and the full Zazopoulos case study.

The starting point

The client - Zazopoulos SA, a 30-year-old wholesale distributor in northern Greece - sells around 65,000 SKUs across home goods and small appliances to about 400 active business customers. Every customer has their own price list, their own credit terms, and a long memory for what they paid last month. Their system of record was Entersoft Business Suite, sitting on an on-premise SQL Server, with no web-facing surface.

The job to be done was unromantic: replace the phone/email/fax intake with a B2B portal that knew each customer’s correct price, current credit, and live stock - without forcing the back-office team to change how they used Entersoft. They were not going to migrate ERP. We were not going to fork it.

Our brief: build the customer-facing surface as a Laravel + WooCommerce stack, keep Entersoft as the system of record, and put a piece of middleware between them that nobody - neither the warehouse staff nor the web visitors - would ever have to think about.

The architecture, in one picture

┌─────────────────┐     pull     ┌────────────────┐    write    ┌──────────────┐
│ Entersoft ERP   │ ───────────► │   Middleware   │ ──────────► │ WooCommerce  │
│ (SQL Server,    │              │   (Laravel +   │             │  (MySQL)     │
│  on-premise)    │ ◄─────────── │    queues)     │ ◄────────── │              │
└─────────────────┘   orders     └────────────────┘   webhooks  └──────────────┘
                                        │
                                        ▼
                                ┌────────────────┐
                                │ Redis (cache + │
                                │  outbox/queue) │
                                └────────────────┘

Three cadences ran independently:

  • Inventory: pulled every 5 minutes via a delta query against an Entersoft-side view, bulk-upserted into WooCommerce as plain stock writes.
  • Products & per-customer pricing: pulled every 30 minutes with a ModifiedAfter watermark, with customer-specific prices written into a side-table keyed by (customer_id, sku) so the catalogue itself stayed multi-tenant-clean.
  • Orders: pushed on the WooCommerce woocommerce_thankyou hook into an outbox table, then drained by a Laravel queue worker that called Entersoft with an idempotent externalReference equal to the Woo order id.

The shape is unsexy on purpose. The whole project was a bet that boring patterns, applied carefully, would outperform anything clever.

#1 - “Price” is not one number

The first naïve cut of the integration treated price as a single field per SKU. It worked for two days. Then a customer-specific override fired, a volume bracket fired on top of it, and a quarterly promo fired on top of that, and we shipped a £0.00 line to a real customer.

The fix was to stop pretending we could compute the price on the WooCommerce side. We pushed the entire price calculation into Entersoft, called it on the fly for every logged-in cart, memoised it for 30 minutes per (customer, sku, qty), and invalidated the cache when any ProductsModifiedAfter hit returned a row touching the SKU. The Entersoft round-trip added ~80 ms to a logged-in product view - visible, but acceptable, and far cheaper than the alternative of rebuilding Entersoft’s rule chain on our side.

Lesson, in one line: do not re-implement your ERP’s pricing logic. Call it.

#2 - The order outbox we almost did not build

Early prototypes posted the order directly to Entersoft inside the woocommerce_thankyou handler. It is the obvious shape: customer pays, hook fires, order goes in. Then the on-prem Entersoft host had a 90-second blip during a Windows update. Three customer-paid orders silently disappeared into a SOAP timeout and never reached the ERP. We found out the next morning when a customer called.

We rewrote it that afternoon. Every order goes into an outbox table the instant the payment is captured. A Laravel queue worker drains the outbox, posts to Entersoft with an externalReference equal to the Woo order id, and writes the returned ERP id back next to the row. If the call fails, the worker retries with exponential backoff. If Entersoft is down for an hour, orders just stack in the outbox; nothing is lost.

Lesson: writes to a downstream system you do not own belong in an outbox, not in a request handler. Always.

#3 - Inventory truth at three places at once

WooCommerce, Entersoft, and the warehouse floor each had their own opinion about how much stock existed. A pallet count from a manual stocktake on a Tuesday afternoon could disagree with the ERP by half a unit; the ERP could disagree with what Woo had cached by another half. The first time a B2C-style flash sale ran on a B2B SKU, we oversold by 11 units across 4 customers.

The fix had two parts. First, we declared Entersoft the single source of truth and rewrote the integration to make WooCommerce a strictly read-only follower for stock. Second, we shortened the pull cadence from 15 to 5 minutes, accepted the marginal extra load, and added a small per-SKU soft reservation in Redis at the moment of cart add - released after 15 minutes if checkout did not complete. The result was zero oversells for the next six months.

Lesson: have one source of truth for stock, no exceptions, and treat the customer-facing layer as a cache.

Where it landed

Eight months after go-live, measured against the same period the previous year:

  • −70% internal order-entry time (the headline number)
  • +45% online order volume; the share of orders coming through the portal vs the phone/email channel rose from 0% to 62%
  • 0 pricing disputes raised by customers, vs an average of ~3 per month previously
  • ~80 ms overhead per logged-in product view from the live price call (acceptable trade-off)
  • 0 oversells after the inventory rewrite

None of those numbers come from clever code. They come from being disciplined about the boring patterns: one source of truth, idempotent writes, an outbox for everything that crosses a system boundary, cache invalidation tied to a watermark you trust.

What we’d change next time

Two things. First, we would put the outbox in from day one rather than after the first incident - the “direct post in the handler” phase cost us a real customer call, and the rewrite was a day’s work we did not need to do under pressure. Second, we would model price as a function rather than a value in the data model itself, not just in the runtime; making it a field invites someone to cache it badly six months later.

Neither is technically interesting. Both would have made the project boring sooner, which is exactly what you want from this kind of integration.

Σχεδιάζετε ένα παρόμοιο έργο;

Πείτε μας για το stack σας και τι θέλετε να ολοκληρώσετε. Απαντάμε εντός 2 εργάσιμων ημερών με ξεκάθαρο σκοπό και ενδεικτική τιμολόγηση.

Ζητήστε Δωρεάν Συμβουλευτική ← Πίσω στο Blog