Skip to main content

Mage2Plenty v3.5 - Placeholder SKU Quarantine & Self-Healing Stock Sync

· 8 min read
Soft Commerce Team
Mage2Plenty Development Team

We're pleased to announce Mage2Plenty v3.5, a stability release that closes the remaining gaps around PlentyONE-driven SKU lifecycle. The import pipeline now quarantines PlentyONE placeholder SKUs (e.g. NEW-1234) so they never reach catalog_product_entity, the stock pipeline self-heals when those placeholders are later renamed to their final SKU, and a new plenty:stock:reconcile command diagnoses and re-pends rows that have drifted out of sync. variation.number is now formally the authoritative identifier across the import pipeline; variation.sku is deprecated.

What's New in v3.5

This release follows the v3.4 item-mapping integrity work with the next layer of the same investigation: what happens when PlentyONE assigns a temporary NEW-XXXX SKU to an item created on its side, and then later renames it to the operator's final SKU. Before v3.5, those placeholders could create short-lived Magento products that then corrupted the stock mapping when the rename happened. The fix touches the import gate, the stock pre-processors, and the resource layer underneath them.

Placeholder SKU Quarantine

The Problem

PlentyONE assigns its own placeholder SKU (commonly NEW-1234, NEW-1235, …) the moment an item is created on its side, before the operator has had a chance to enter the final SKU. The Mage2Plenty import was previously identifier-agnostic and would happily create a catalog_product_entity row with sku = NEW-1234, populate plenty_item_id / plenty_variation_id, and write the corresponding plenty_variation_entity row. When the operator later renamed the PlentyONE variation to the real SKU, the next import resolved a different Magento product (or none at all), leaving the old NEW-1234 row orphaned and pushing a stale (plenty_item_id, plenty_variation_id) pair onto whichever product happened to win the next mapping pass.

The Fix

A new profile setting Placeholder SKU Pattern (admin field on the item-import profile form, empty = disabled) accepts a regex that gates ItemImportService::canProcess(). Recommended value: ^NEW-\d+$. When the variation number matches the regex:

  • The row is skipped with a NOTICE-level message in profile history.
  • The variation stays in pending — no Magento product is created, no mapping is written.
  • The next import re-evaluates the variation; once the operator finalises the SKU upstream, the row stops matching the regex and flows through normally.

StoreConfig::isPlaceholderSku() fails-closed on malformed regex (returns false, logs a warning), so a typo in the admin field cannot block legitimate imports.

variation.number Is Now Authoritative

variation.sku has been the deprecated identifier on plenty_variation_entity for several releases — the collector no longer populates it, and Variation::getSku() has been a thin wrapper for getNumber(). v3.5 formalises the deprecation:

  • VariationInterface::SKU and Variation::getSku() are marked @deprecated in PHPDoc.
  • Variation\ResourceModel::getNumberByVariationIds([id]) is the new authoritative bulk lookup, returning [variation_id => number] from the number column only.
  • The legacy getNumberSkuPairsByVariationIds() is kept as a @deprecated wrapper that mirrors number into both keys, preserving backward compatibility for external callers.
  • Remaining $variation->getSku() reads in ImportItemCommand, UrlRewrite error metadata, and the Configurable type processor (composite-data check, post-merge message, child-error aggregation) are now getNumber().

External modules that depend on VariationInterface::SKU or getSku() keep working; the wrappers will be removed in a future major release.

Self-Healing Stock Pre-Processors

The Problem

SkuAssignment and NumberAssignment in StockImportService used to trust the (sku, product_id, number) values stored on plenty_stock_entity once they had been populated. That worked when nothing changed upstream, but produced silent stale mappings in two cases:

  1. A placeholder SKU was imported, mapped to a Magento product, then the upstream variation was renamed — plenty_stock_entity.sku continued to reflect the old placeholder.
  2. The Magento product was deleted but the plenty_stock_entity row survived — subsequent imports stamped a complete status against an unresolvable mapping.

The Fix

Both pre-processors now treat stored values as a stale-cacheable mirror, not source of truth:

  • Every run re-resolves (sku, product_id, number) from ProductMappingStrategy and the live plenty_variation_entity row.
  • When no Magento product resolves, the stored values are cleared and the row is left for the validator to mark SKIPPED — preventing the previous behaviour of stamping COMPLETE against a broken mapping.
  • Drift between stored and freshly resolved values is logged at INFO to the PlentyStock virtual logger for audit, without changing the row's run status.

Variation Rename Cascade

A new plugin Plugin\PlentyItem\InvalidateStockOnNumberChange hooks Variation::insertOnDuplicate (the bulk upsert path used by the item collector) and detects when an existing plenty_variation_entity row's number has changed. For every affected variation it calls the new Inventory::invalidateMappingByVariationIds([ids]) helper, which clears (sku, product_id) and sets status = pending on the corresponding plenty_stock_entity rows in a single statement.

This means: when a NEW-1234 placeholder is renamed to the final SKU in PlentyONE, the next item collect picks up the rename, the plugin re-pends the matching stock rows, and the next stock import resolves them fresh through ProductMappingStrategy — no stale mapping carried forward, no operator intervention required.

The plugin lives in module-plenty-stock-profile, with the corresponding module.xml sequence and a composer.json dep on module-plenty-item to guarantee load order.

Reservation Orphan Detection - Anchored on variation_id

CleanupClientReservations previously joined orphan reservations on p.sku = cpe.sku to find rows whose Magento product no longer exists. With the self-healing pre-processors now clearing stale (sku, product_id) values during reconciliation windows, the SKU column became too volatile for that join — it produced false positives that could delete live reservations mid-cycle.

The detection now joins on p.variation_id = cpe.plenty_variation_id (an immutable column populated when the mapping was first established) and adds a cpe.plenty_variation_id IS NOT NULL guard so unmapped products are left alone entirely. The 500/run safety cap and SKU-existence sanity check introduced in v3.4 are unchanged.

Source-Assignment Notice

A new post-processor SourceAssignmentCheck (sortOrder 5, runs before SaveEntity) emits a NOTICE-level message in profile history when, after a successful stock-import run for a given (sku, source_code), no matching inventory_source_item row exists. The row still completes as COMPLETENOTICE doesn't downgrade run status — but the signal surfaces in the profile-history viewer for operator follow-up and is the trigger for running the new reconcile command.

This is the operator-facing complement to the v3.4 zero-qty source-item fix: that fix prevents the row from going missing in the first place; this notice surfaces any case where it nonetheless ends up missing (e.g. a pre-processor cleared the mapping and no fix-up ran in time).

Price Attribute Dropdown - "None" Persistence

The None option added to the Magento price attribute dropdown in v3.3 used null as its underlying value. Magento's UI form serialization does not round-trip null reliably for select options — selecting None appeared to work in the form but the cleared mapping could not be persisted on save. The value now uses '0' as its unset sentinel; attribute IDs are positive integers, so 0 is unambiguous and serializes cleanly through the admin form. Admins can finally clear a price mapping and have it stick.

plenty:stock:reconcile Console Command

A new diagnostic and repair command at bin/magento plenty:stock:reconcile walks plenty_stock_entity and reports three drift patterns:

  1. Orphan product_id — row references a Magento product that no longer exists. With --fix, the row is re-pended.
  2. Unresolved mapping resolvable by numberproduct_id is null but plenty_variation_entity.number matches a catalog_product_entity.sku. With --fix, the row is re-pended to let ProductMappingStrategy repopulate it.
  3. Variation-number ↔ Magento-SKU driftplenty_stock_entity.skuplenty_variation_entity.number. Report-only; cannot be auto-fixed because the correct resolution depends on upstream intent.

Without --fix the command is fully read-only and prints a count + sample per drift pattern. The command is intended for post-incident verification or as the manual follow-up when the new source-assignment notice fires.

Release Summary

ModuleVersionBumpKey Changes
module-plenty-item-profile3.4.0minorPlaceholder SKU quarantine, variation.number across import pipeline
module-plenty-stock-profile2.2.0minorSelf-healing pre-processors, rename cascade plugin, source-assignment notice, plenty:stock:reconcile command, variation_id-anchored orphan cleanup
module-plenty-stock2.1.0minorinvalidateMappingByVariationIds helper on Inventory resource
module-plenty-item2.4.2patch@deprecated on VariationInterface::SKU / getSku(), new getNumberByVariationIds resource method

Upgrade Guide

Prerequisites

  • Magento 2.4.6+ (2.4.8 recommended)
  • PHP 8.1+ (8.3 or 8.4 recommended)
  • Mage2Plenty v3.4.x

Quick Upgrade

composer require softcommerce/mage2plenty-os:^3.5

bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento cache:flush

Post-Upgrade Steps

  1. Set the placeholder SKU pattern. On each item-import profile, set Placeholder SKU Pattern to ^NEW-\d+$ (or the regex matching your PlentyONE placeholder convention). Leaving it empty preserves pre-v3.5 behaviour; this is opt-in per profile.

  2. Run the reconcile command once. Surfaces any pre-existing drift left over from before the self-healing pre-processors were in place:

    bin/magento plenty:stock:reconcile

    Add --fix only after reviewing the report. The fix path is conservative — it re-pends rather than deletes — so the next stock import re-resolves the mapping.

  3. No action required for the variation-rename cascade, the source-assignment notice, or the reservation-orphan join change — all take effect immediately on the next import / cleanup cycle.

  4. Third-party integrators: Migrate any direct reads of VariationInterface::SKU or $variation->getSku() to getNumber(). Both call sites still work via deprecated wrappers but will be removed in a future major release.

Resources


Questions about the upgrade? Reach out to us at support@byte8.io.