How Synplex Tracks Assembly-to-Order Inventory
This page explains the two automated mechanics that keep assembly-to-order (ATO) BOMs accurate: buildable quantity calculation (driven by component inventory changes) and Shopify inventory sync (writing the result back to the parent SKU in Shopify).
These run automatically once a BOM is active. You do not need to trigger them manually.
Why ATO BOMs Need Continuous Recalculation
A pre-assembly kit holds real finished goods stock on the parent SKU. Shopify manages that number directly.
An ATO BOM is different. The parent SKU holds no finished goods. Its "available" figure in Shopify should reflect how many units could be assembled right now given current component stock. Every time a component's inventory changes — a sale, a receipt, a manual adjustment — the parent SKU's available figure becomes stale and must be recalculated.
The cascade works as follows:
- A component's stock changes
- Buildable quantity is recalculated per location
- The new figure is written back to the parent SKU in Shopify
- Shopify shows accurate available-to-promise to customers
Part 1: Buildable Quantity Calculation
What triggers a recalculation
A recalculation is enqueued whenever a component's inventory level changes. This is handled by the recalculateBomWorker background action, called with the bomId and the locationId where the change occurred.
How buildable quantity is calculated
For each component in the BOM, Synplex reads the available stock at the specified location and divides it by the required quantity per finished unit. The lowest result across all components is the buildable quantity. The component responsible for the lowest result is the bottleneck.
Example — BOM: "Custom PC Build – Base"
| Component | Required qty per unit | Stock at London Warehouse | Buildable |
|---|---|---|---|
| CPU (Intel i5) | 1 | 120 | 120 |
| RAM 16GB | 2 | 90 | 45 ← bottleneck |
| SSD 512GB | 1 | 200 | 200 |
| Buildable quantity at London Warehouse: 45 units. Bottleneck: RAM 16GB. |
The formula: buildable = min over all components of floor(available ÷ required_qty)
If any component has no inventory level record at the location, buildable quantity is treated as 0 for that location.
Per-location granularity
The calculation runs independently per location. If your shop has three warehouses, Synplex produces three separate buildable quantities and tracks the bottleneck component independently for each.
The overall buildable quantity shown in the BOM table is the sum across all included locations.
Part 2: Shopify Inventory Sync
What gets synced and when
After the buildable quantity is calculated, Synplex writes the new figure to the parent SKU's inventory level in Shopify using the inventorySetQuantities GraphQL mutation (reason: correction).
This sync only runs for ATO BOMs. Pre-assembly kits are excluded because their parent SKU inventory is managed by Shopify directly through Production Orders, not derived from component stock.
- BOM type: assemble-to-order → sync runs
- BOM type: pre-assembled → sync skipped
Change detection
Synplex compares the new buildable quantity against the current value stored in the inventory level record before calling Shopify. If the values are identical, the Shopify API call is skipped entirely, avoiding unnecessary API calls and rate limit consumption.
Example — no change needed:
- Previous available in Shopify: 45
- New buildable quantity: 45
- Result: Shopify call skipped
Example — sync required:
- Previous available in Shopify: 45
- New buildable quantity: 38
- Result: sync runs, Shopify updated to 38
What Shopify receives
The inventorySetQuantities mutation is called with:
reason:"correction"name:"available"inventoryItemId:gid://shopify/InventoryItem/{id}locationId:gid://shopify/Location/{id}quantity: the new buildable quantity
Sync log
Every sync attempt — whether it succeeds or fails — is recorded in the InventorySyncLog model. Each record captures:
- bom — which BOM triggered the sync
- location — which location was synced
- productVariant — the parent SKU
- previousInventoryLevel — value before sync
- actualInventoryLevel — value written
- delta — change applied (positive or negative)
- reason —
"bom_recalculation" - success — true / false
- errorMessage — populated on failure
You can review sync history in the app to diagnose discrepancies between Synplex and Shopify.
What Happens When a BOM Status Changes
Status transitions on an active BOM trigger additional cascading updates beyond the buildable quantity sync.
Activating a BOM (draft → active)
bomStatusfield synced to"active"on all BomComponent recordsisBomComponentflag set totrueon all component inventory levelsrecalculateComponentDemandHistoryenqueued for each component (see the demand explosion doc for what this does)- First buildable quantity calculation enqueued per location
Archiving a BOM (active → archived)
bomStatusfield synced to"archived"on all BomComponent recordsisBomComponentflag set tofalseon all component inventory levelsrecalculateComponentDemandHistoryenqueued for each component (zeroes out the BOM demand contribution)- Parent SKU inventory in Shopify is no longer updated by Synplex
Reactivating a BOM (archived → active)
Same cascade as activation. All flags are reset and demand recalculation re-runs from scratch.
Changing BOM type on an active BOM
If an active BOM is switched between pre-assembled and assemble-to-order, Synplex re-enqueues recalculateComponentDemandHistory for all components because the demand attribution logic differs between the two types.
Troubleshooting
Parent SKU shows wrong available in Shopify
- BOM status is "active" — draft BOMs do not sync
- BOM type is "assemble-to-order" — pre-assembled BOMs do not sync
- Check the InventorySyncLog for this BOM — was the last sync successful?
- Component inventory levels exist at the location in question
- No component is missing an
inventoryItemIdlinkage
Buildable quantity shows 0 unexpectedly
- At least one component has 0 stock at the location
- A component is missing its
inventoryItemId(visible in BOM detail) - The location is marked as "included" in Synplex location settings — excluded locations are not used in calculations
Sync succeeded but Shopify shows a different number
Possible causes:
- Shopify applied its own inventory adjustment after the sync (e.g. a sale processed between sync and your check)
- Another app or manual edit overwrote the value in Shopify
- The sync ran against a different location than expected
Questions? Contact support@synplex.dev