Skip to content

Mark implicitly created composite stock items as automatically maintained - #41175

Open
brosenberger wants to merge 1 commit into
magento:2.4-developfrom
brosenberger:36154-composite-stock-status-changed-auto
Open

Mark implicitly created composite stock items as automatically maintained#41175
brosenberger wants to merge 1 commit into
magento:2.4-developfrom
brosenberger:36154-composite-stock-status-changed-auto

Conversation

@brosenberger

Copy link
Copy Markdown
Contributor

Description (*)

A composite product's stock status is derived from its children, so the legacy stock item created implicitly on product save records no merchant decision. It is left with stock_status_changed_auto = 0, which is indistinguishable from a merchant having taken the product off sale.

ChangeParentStockStatus::isNeedToUpdateParent() lets a parent go out of stock unconditionally but only lets it come back when that flag is set, so a composite created before its children are linked — the ordinary structure-feed import order — is latched out of stock permanently. Attaching the children later does not clear it, and neither does stocking them; the flag is stored, not derived, so reindexing cannot help.

This fixes the input, not the predicate. isNeedToUpdateParent() is unchanged in all three modules and still protects a merchant's manual out-of-stock decision. Only a row that records no decision at all is now marked as automatically maintained. The change sits in the existing non-qty branch of StockItemRepository::save(), which already isolates exactly the composite types — StockConfigurationInterface::isQty() is false for precisely configurable, bundle and grouped, since only simple, virtual and downloadable declare isQty="true". No new type check is introduced.

The asymmetry being corrected is visible in the method itself. The qty branch maps the "changed automatically" marker onto the column; the composite branch never touched the column at all:

if ($isQty) {
    …
    if ($stockItem->hasStockStatusChangedAutomaticallyFlag()) {
        $stockItem->setStockStatusChangedAuto((int)$stockItem->getStockStatusChangedAutomaticallyFlag());
    }
} else {
    $stockItem->setQty(0);
}

This also generalises a rule the platform already applies: Magento\InventoryConfigurableProduct\Model\StockStatusManagement::update() derives the flag for a newly created stock item, but only for configurables, only in single source mode, and it computes 0 for the born-out-of-stock case.

Related Pull Requests

magento/inventory#3467 — related test coverage in the Inventory repository. The multi-source half of this area is not fixed here; see magento/inventory#3466 for why it needs a design decision rather than a bug fix.

Fixed Issues (if relevant)

  1. Fixes Composite product created before its children are linked is latched out of stock permanently #41174
  2. Fixes Configurable stock status is not correct when using custom stock (MSI) #36154
  3. Fixes Grouped product stock status not changing to in stock when its child is added with qty and set as in stock through rest api #37960
  4. Related to Import. Configurable's stock is not updated when child's stock changed by import. #32192

Manual testing scenarios (*)

  1. Install 2.4-develop, single source mode.
  2. POST /rest/V1/products — create a grouped (or configurable, or bundle) parent with no stock_item extension attribute and no children linked yet.
  3. Read the parent's row:
    SELECT is_in_stock, stock_status_changed_auto
    FROM cataloginventory_stock_item si
    JOIN catalog_product_entity e ON e.entity_id = si.product_id
    WHERE e.sku = '<parent sku>';
    Before this change: (0, 0). After: (0, 1).
  4. Create two simple children with no stock, and attach them to the parent in a second call.
  5. Send stock for one child in a third call.
  6. Re-read the parent's row.
    • Before: still (0, 0). The parent is out of stock on the storefront and no amount of reindexing changes it.
    • After: (1, 1). The parent follows its children.
  7. Set every child out of stock and re-read: the parent follows them out, so this is not "force in stock".
  8. In the admin, set the parent out of stock by hand, then restock a child. The parent stays out of stock — the merchant's decision is still respected.
  9. Repeat for the other two composite types.

Boundary worth knowing: the parent must be created before its children are linked. If a single call creates the parent already carrying its links or options, ChangeParentStockStatus runs inside that same save, the parent is born (0, 1) and recovers normally. That is why this defect looks intermittent between integrations.

Questions or comments

isNeedToUpdateParent() is duplicated verbatim in ConfigurableProduct, Bundle and GroupedProduct. This PR deliberately leaves all three untouched; happy to consolidate them in a follow-up if maintainers prefer.

Contribution checklist (*)

  • Pull request has a meaningful description of its purpose
  • All commits are accompanied by meaningful commit messages
  • All new or changed code is covered with unit/integration tests (if applicable)
  • README.md files for modified modules are updated and included in the pull request if any README.md predefined sections require an update
  • All automated tests passed successfully (all builds are green)

…atically maintained

A composite product's stock status is derived from its children, so the legacy
stock item created implicitly on product save records no merchant decision. It
was left with stock_status_changed_auto = 0, which is indistinguishable from a
merchant having taken the product off sale.

ChangeParentStockStatus lets a parent go out of stock unconditionally but only
lets it come back when that flag is set, so a composite created before its
children are linked - the ordinary structure-feed import order - is latched out
of stock permanently. Attaching the children later does not clear it and neither
does stocking them; the flag is stored, not derived, so reindexing cannot help.

Fix the input rather than the predicate: isNeedToUpdateParent() is unchanged and
still protects a merchant's manual out-of-stock decision. Only a row that records
no decision at all is now marked as automatically maintained. The change sits in
the existing non-qty branch of StockItemRepository::save(), which already isolates
exactly the composite product types, so no new type check is introduced.

This generalises a rule upstream already applies in
InventoryConfigurableProduct\Model\StockStatusManagement::update(), which derives
the flag for a newly created stock item but only for configurables and only in
single-source mode, and computes 0 for the born-out-of-stock case.

Covered by integration tests for configurable, bundle and grouped, plus unit
coverage of the new branch. Two of the integration tests pass before and after the
change, pinning that a parent created with its children still recovers and that a
merchant's manual out-of-stock decision survives.
@m2-assistant

m2-assistant Bot commented Aug 31, 2026

Copy link
Copy Markdown

Hi @brosenberger. Thank you for your contribution!
Here are some useful tips on how you can test your changes using Magento test environment.
❗ Automated tests can be triggered manually with an appropriate comment:

  • @magento run all tests - run or re-run all required tests against the PR changes
  • @magento run <test-build(s)> - run or re-run specific test build(s)
    For example: @magento run Unit Tests

<test-build(s)> is a comma-separated list of build names.

Allowed build names are:
  1. Database Compare
  2. Functional Tests CE
  3. Functional Tests EE
  4. Functional Tests B2B
  5. Integration Tests
  6. Magento Health Index
  7. Sample Data Tests CE
  8. Sample Data Tests EE
  9. Sample Data Tests B2B
  10. Static Tests
  11. Unit Tests
  12. WebAPI Tests
  13. Semantic Version Checker

You can find more information about the builds here
ℹ️ Run only required test builds during development. Run all test builds before sending your pull request for review.


For more details, review the Code Contributions documentation.
Join Magento Community Engineering Slack and ask your questions in #github channel.

brosenberger pushed a commit to brosenberger/module-composite-stock-status that referenced this pull request Aug 31, 2026
Splits the Upstream section by defect. The latch half now points at
magento/magento2#41174 and the core fix in magento/magento2#41175; the multi-source
half keeps magento/inventory#3466 and #3467.

Notes what happens if the core fix lands: KeepCompositeStockAutomatic becomes
redundant on the fixed versions, but only for products created after the upgrade -
catalogues already carrying latched parents still need one revive run.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant