Skip to content

Add SKU-based lookup for Inventory Synchronization#471

Draft
faisal-alvi wants to merge 4 commits intotrunkfrom
SQUARE-264
Draft

Add SKU-based lookup for Inventory Synchronization#471
faisal-alvi wants to merge 4 commits intotrunkfrom
SQUARE-264

Conversation

@faisal-alvi
Copy link
Copy Markdown
Collaborator

@faisal-alvi faisal-alvi commented Mar 11, 2026

All Submissions:

  • Does your code follow the WooCommerce Sniffs variant of WordPress coding standards?
  • Have you written new tests for your changes, as applicable?
  • Have you successfully run tests with your changes locally?
  • Will this change require new documentation or changes to existing documentation?

Changes proposed in this Pull Request:

When push_inventory runs, products/variations without a stored _square_item_variation_id mapping were previously skipped. If an earlier upsert_new_products step timed out (or failed) after Square created the item but before the plugin saved the mapping, those products stayed at 0 quantity in Square and never received a stock push.

This PR adds SKU-based fallback so push_inventory can recover in that case:

  • A new method added Product::get_square_variation_id_by_sku()
    Looks up a Square catalog variation by SKU via SearchCatalogObjects (exact query on sku). Optionally persists the mapping so later sync steps use the stored ID. Exceptions are caught and logged; the method returns null so sync continues.
  • push_inventory (Manual_Synchronization)
    • For simple products: if there is no stored Square variation ID but the product has a SKU and manages stock, attempts the new SKU lookup, persists the mapping when found, then builds and pushes the inventory change.
    • For variable products: same logic per child variation (no mapping → try SKU lookup → persist when found → push inventory).
    • MAX_SKU_LOOKUPS_PER_PUSH_STEP = 20 limits how many SKU lookups run per push_inventory step to avoid rate limits; additional unmapped products are handled in later step runs.

Note

The timeout that prevents mapping from being saved is intermittent (e.g. slow API, server limits), so this PR does not address that root cause. Instead, it targets the incomplete-sync case: when products already exist in Square but WooCommerce never received or stored the variation IDs (e.g. after a timeout). For those unmapped products, the plugin now attempts a SKU-based lookup during push_inventory—matching the approach suggested in the original issue—and persists the mapping when found so inventory can be pushed. This makes inventory push resilient to missed meta and looks promising for the reported scenario.

Closes https://linear.app/a8c/issue/SQUARE-264/push-inventory-should-attempt-sku-based-matching-for-unmapped-products.

Steps to test the changes in this Pull Request

  1. Set Woo as system of record
  2. Inventory sync enabled
  3. Create a new simple product with SKU and stock
  4. Run manual sync so it upserts to Square.
  5. In DB, delete the product’s _square_item_variation_id meta (simulate lost mapping).
  6. Run sync again
  7. Confirm the Square inventory for that product matches Woo after sync
  8. Repeat with a variable product; remove only one variation’s _square_item_variation_id.
  9. Run sync; confirm that variation’s inventory is pushed to Square after SKU lookup.
  10. Confirm already-mapped products still push inventory as before (no behavior change).
  11. Confirm products without SKU or that don’t manage stock are still skipped for push (no new behavior for them).

Changelog entry

Fix - Push inventory now attempts SKU-based lookup for synced products missing a Square variation ID

@faisal-alvi faisal-alvi self-assigned this Mar 11, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 11, 2026

🔍 WordPress Plugin Check Report

❌ Status: Failed

📊 Report

🎯 Total Issues ❌ Errors ⚠️ Warnings
8 8 0

❌ Errors (8)

📁 includes/Admin/Product_Editor_Compatibility.php (1 error)
📍 Line 🔖 Check 💬 Message
0 missing_direct_file_access_protection PHP file should prevent direct access. Add a check like: if ( ! defined( 'ABSPATH' ) ) exit;
📁 includes/WC_Payments_Compatibility.php (1 error)
📍 Line 🔖 Check 💬 Message
0 missing_direct_file_access_protection PHP file should prevent direct access. Add a check like: if ( ! defined( 'ABSPATH' ) ) exit;
📁 includes/Framework/PaymentGateway/Admin/views/html-user-profile-field-customer-id.php (1 error)
📍 Line 🔖 Check 💬 Message
0 missing_direct_file_access_protection PHP file should prevent direct access. Add a check like: if ( ! defined( 'ABSPATH' ) ) exit;
📁 includes/Framework/PaymentGateway/Admin/views/html-order-partial-capture.php (1 error)
📍 Line 🔖 Check 💬 Message
0 missing_direct_file_access_protection PHP file should prevent direct access. Add a check like: if ( ! defined( 'ABSPATH' ) ) exit;
📁 includes/Framework/PaymentGateway/Admin/views/html-admin-gateway-status.php (1 error)
📍 Line 🔖 Check 💬 Message
0 missing_direct_file_access_protection PHP file should prevent direct access. Add a check like: if ( ! defined( 'ABSPATH' ) ) exit;
📁 includes/Framework/PaymentGateway/Admin/views/html-user-payment-token-editor.php (1 error)
📍 Line 🔖 Check 💬 Message
0 missing_direct_file_access_protection PHP file should prevent direct access. Add a check like: if ( ! defined( 'ABSPATH' ) ) exit;
📁 includes/Framework/PaymentGateway/Admin/views/html-user-payment-token-editor-token.php (1 error)
📍 Line 🔖 Check 💬 Message
0 missing_direct_file_access_protection PHP file should prevent direct access. Add a check like: if ( ! defined( 'ABSPATH' ) ) exit;
📁 includes/Framework/PaymentGateway/Admin/views/html-user-profile-section.php (1 error)
📍 Line 🔖 Check 💬 Message
0 missing_direct_file_access_protection PHP file should prevent direct access. Add a check like: if ( ! defined( 'ABSPATH' ) ) exit;

🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

@faisal-alvi faisal-alvi requested a review from iamdharmesh March 12, 2026 08:42
Copy link
Copy Markdown
Collaborator

@iamdharmesh iamdharmesh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR @faisal-alvi. Have some questions could you please help answer it?

If an earlier upsert_new_products step timed out (or failed) after Square created the item but before the plugin saved the mapping, those products stayed at 0 quantity in Square and never received a stock push.

If upsert_new_products step timed out, it will be retried and product upsertion will be retried. So, I don't anticipate the background process will reach to the push_invetory step without adding a reference to the meta. @faisal-alvi have you tried reproducing the issue by adding an intentional wait in the upsert_new_products function and making it timed out? Is it reaching to the push_invetory step? Thanks


if ( $child instanceof \WC_Product && $child->get_manage_stock() && $inventory_change ) {
$child_square_id = Product::get_square_item_variation_id( $child_id, false );
if ( ! $child_square_id && $child->get_sku() && $sku_lookups_this_step < self::MAX_SKU_LOOKUPS_PER_PUSH_STEP ) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will happen when $sku_lookups_this_step becomes greater than self::MAX_SKU_LOOKUPS_PER_PUSH_STEP? Will it be skipped for the rest of the products?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cap is there to bound SearchCatalogObjects traffic per step to reduce rate-limit risk; so the limit applies per run of that step, not for the entire sync forever.

If the cap is reached within that run, we stop doing further SKU lookups for remaining unmapped lines in that same run. The push_inventory step keeps scheduling until inventory_push_product_ids is empty, so subsequent runs reset the counter and can perform more lookups. Successful lookups persist the variation ID, so those SKUs typically won’t need another lookup.

@faisal-alvi
Copy link
Copy Markdown
Collaborator Author

@iamdharmesh You’re right that we don’t call complete_step( 'upsert_new_products' ) until the upsert queue is drained, and that inventory_push_product_ids is only merged from upsert_catalog_objects() after that method returns. So under normal operation we shouldn’t advance to push_inventory while a batch is still “in flight” inside upsert_catalog_objects.

I agree that reproducing the original report purely by “slowing upsert_new_products until timeout” may not mirror the failure mode unless we kill the process after Square returns and before job/product state is fully persisted, or we hit an operational edge case (meta removed from DB, restore, cache, etc.).

So the SKU lookup in push_inventory is partly defense in depth: if Woo is missing_square_item_variation_id but Square already has a matching ITEM_VARIATION for the SKU, we recover instead of skipping the physical count. I’m happy to tighten the PR wording so it doesn’t over-claim the timeout scenario versus “missing mapping for any reason.”

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants