An OpenCart store rarely breaks in a single day. First a theme is bought, then a shipping module, then a feed for a marketplace, and then someone adds a tweak to the cart. A few years later the owner can no longer say what in their engine is built in and what was bolted on top. When OpenCart or a module is updated, these customizations have to be checked and often rewritten.
This article is for those who are already on OpenCart or are choosing it. It does not cover what is visible from the outside across 43 stores on three platforms — that is in a separate article, "Prom, Horoshop or OpenCart". Here we check the core itself: four places where a catalog most often runs into the architecture, and how to check each of them in your own store without a developer.
What OpenCart gives you out of the box, and what you don't need to look for in modules
Let's start with what people often buy for nothing. Simple wholesale pricing is already in the core: on the Discount tab of the product card you set a price for a customer group, a minimum quantity, a priority and validity dates. In the database this is a separate table, oc_product_discount, with fields for group, quantity, priority, price and dates. If your wholesale is "a dealer pays less from 10 units," you don't need a module for it.
Likewise, the product card has built-in Model and SKU fields, the quantity in stock and the Subtract Stock switch, i.e. whether to deduct stock when an order is placed. Product option values have their own quantity and their own price markup.
Now to where the built-in features are no longer enough.
When OpenCart themes and modules are no longer enough: four checks
Below are four signs, and you can check each of them in your own admin panel.
Several warehouses, but one quantity
Take an item that sits in two places and try to tell from the admin panel how much of it is in each. The product card has one Quantity field, and this is not an interface limitation: in both the 3.0 and 4.1 branches, quantity is stored as a single integer in the product table, next to a single price. The default schema has no warehouse table.
If the breakdown lives in a manager's spreadsheet, you are already at the limit: everything that reads the built-in quantity field sees one number. We covered why a single price breaks down with a second warehouse and different purchase costs in the article "Multiple warehouses, one product price — it doesn't work".
Wholesale for which group and quantity are not enough
The built-in discount knows two things: the customer group and the quantity. The limit is where the price starts to depend on something third. Most often that is the warehouse an order ships from, or a personal arrangement with a specific buyer on a specific item. Another typical requirement is a minimum order amount for wholesale.
Ask your manager to write down how they actually calculate the price for your most complicated regular customer. If it fits into the line "group plus quantity," the core will cope. If "but from this warehouse" or "and for this customer, this brand separately" appears, that logic has to be added with patches to the price calculation, and they can conflict with other modules that modify the same place.
The stock changed, and no one knows why
Yesterday the product card showed 12, today it shows 9. Who changed the number, and when? The default 3.0 schema has history tables for orders, customers, coupons, vouchers and returns. There is no stock movement table. The quantity in the card is simply overwritten — by an order, an import or a manager by hand — and the previous value is not kept anywhere.
If you can't find the answer without server logs and asking around, you don't have inventory accounting — you only have the current figure.
For the 3.0 branch there is one more check. Open the database in phpMyAdmin and look at the Engine column in the table list. The default 3.0.5.1 installer creates 132 of 134 tables on MyISAM, and according to the MySQL documentation this engine does not support transactions. That means several related records of one order can neither be executed together nor rolled back together if something goes wrong midway. In the 4.1 branch the installer already uses InnoDB. Converting the tables to InnoDB can usually be done without changing the platform, but it is worth testing on a copy of the database; it will not add a movement log.
Search doesn't find the part number
For stores selling spare parts, tools and any kind of wholesale, this is the most expensive sign, because the buyer arrives with the part number in the clipboard.
Here is how out-of-the-box OpenCart searches (we read the catalog model code in the 3.0 and 4.1 branches). The name is split into words, and each word is searched as a substring. In both 3.0 and 4.1, Model is compared with the query as a whole, case-insensitively. SKU, EAN and other codes are also compared as a whole in 3.0, and by the beginning of the string in 4.1.
So if the part number AB-1234 is not in the product name, the queries AB 1234, ab1234 or 1234 in the storefront search will mostly return an empty page — try it on your catalog, it takes two minutes. The buyer doesn't know exactly how the code is written in your store and goes wherever it was found.
What can be fixed without changing OpenCart
The typical gaps we found in the market measurement are not in the core. Slow server response, an outdated PHP version, a sitemap with duplicates, a missing canonical — all of this is fixed on the same engine; details are in the measurement of the market's technical baseline.
Wholesale tiers within "group plus quantity" are covered by the Discount tab. Tables are converted to InnoDB. A new theme changes how the storefront looks. If your pain points are here, you don't need to change the platform, and we will say so in the first email. part-number search is a borderline case: it is reworked on OpenCart too, but that is already a modification of the search core, not a setting.
What patches can't fix
The limit is not one sign but several of them coinciding. When the price depends on the warehouse, and orders have to be split between warehouses with a log of stock movements, that is already a data model, and you can't add it as a feature. It has to be built into the core of someone else's engine, and whenever OpenCart or a module is updated, it has to be checked and often rewritten.
For this case we build Stable Shop — LIONEX's own engine on Laravel. In its core, one SKU sits in several warehouses, each with its own price, stock and delivery time, and an order is split automatically. Customer groups have several wholesale tiers, personal prices per item and minimum order amounts. Every stock movement is recorded as a separate row with the author and time, and operations run in transactions with row locking. Search runs on Meilisearch with Ukrainian lemmatization, and a part number is found with spaces, hyphens and dots. The code and data stay on your server.
We are just as direct about what it doesn't have yet: there is no import from OpenCart yet, no reservation of items with a timer in the cart, Rozetka Delivery and Meest are still stubs, and an extension store for third-party developers is still in development. The product is currently being prepared for launch. More details are on the Stable Shop card and the page about a store on its own platform.
When OpenCart remains the right choice
When the task is typical: there is already a ready-made module for it, and it is cheaper to install than to write. If you have one warehouse and prices fit into group and quantity, OpenCart does its job, and rewriting the engine would cost more than it would give. We mostly work with OpenCart and continue doing development on it.
If two or three signs coincided after the four checks, send us your store's address and a description of how prices and stock are calculated. A free catalog review will show what can be fixed on the current engine and what runs into the core. We reply within two hours, and the amount, if it comes to work, is fixed in the contract after the review.