Skip to content

Apply out-of-stock-last ordering on search result pages#1260

Open
boo-code wants to merge 2 commits into
PrestaShop:devfrom
boo-code:fix/search-out-of-stock-last-1236
Open

Apply out-of-stock-last ordering on search result pages#1260
boo-code wants to merge 2 commits into
PrestaShop:devfrom
boo-code:fix/search-out-of-stock-last-1236

Conversation

@boo-code

Copy link
Copy Markdown
Contributor
Questions Answers
Description? On search result pages, the "show out-of-stock products last" behaviour was not applied, while it works on category, manufacturer and other listings. Search results are ordered by relevance, which Adapter\MySQL::computeOrderByField() builds as a FIELD(p.id_product, ...) list from the product pool returned by the core search. That branch returned early, before computeShowLast() (the method that prepends the out-of-stock-last ordering) was reached — so out-of-stock products could appear before in-stock ones in search results only. The fix routes the relevance ordering through computeShowLast() too, so out-of-stock products are pushed to the end while the relevance order is preserved within each group.
Type? bug fix
BC breaks? no
Deprecations? no
Fixed ticket? Fixes #1236.
How to test? A unit test is included (MySQLTest::testGetQueryAppliesOutOfStockLastOnSearchRelevanceOrdering): with PS_LAYERED_FILTER_SHOW_OUT_OF_STOCK_LAST enabled and a search-style query (an id_product pool as the initial population, ordered by position), the generated ORDER BY now contains both the out-of-stock-last clause (IFNULL(p.quantity, 0) <= 0) and the relevance ordering (FIELD(p.id_product,1,2,3)) — before this change only the relevance ordering was present. Manually: enable "show out-of-stock products last", run a storefront search returning both in- and out-of-stock products, and compare with a category listing — they now order stock the same way.
Sponsor company

Before / after, the generated ORDER BY for a search query with the feature enabled:

before: ORDER BY FIELD(p.id_product,1,2,3) DESC
after:  ORDER BY IFNULL(p.quantity, 0) <= 0, IFNULL(p.quantity, 0) <= 0 AND FIELD(sa.out_of_stock, 1) DESC, FIELD(p.id_product,1,2,3) DESC

(The test needed Product to be mockable, so a one-line Product stub was added to the test MockProxy alongside the existing ones.)

On search pages the products are ordered by relevance, built as a FIELD() list
of the product ids returned by the core search. computeOrderByField() returned
that relevance ordering early, before computeShowLast() ran, so the "show out of
stock products last" behaviour - applied to category, manufacturer and other
listings - was skipped for searches. Out-of-stock products could therefore show
up before in-stock ones in search results.

Route the search relevance ordering through computeShowLast() as well, so out of
stock products are pushed to the end while the relevance order is preserved
inside each group.
@ps-jarvis

Copy link
Copy Markdown

Hello @boo-code!

This is your first pull request on ps_facetedsearch repository of the PrestaShop project.

Thank you, and welcome to this Open Source community!

@github-project-automation github-project-automation Bot moved this to Ready for review in PR Dashboard Jun 27, 2026
kpodemski
kpodemski previously approved these changes Jul 6, 2026
@ps-jarvis ps-jarvis added the Waiting for QA Status: Action required, Waiting for test feedback label Jul 6, 2026
@ps-jarvis ps-jarvis moved this from Ready for review to To be tested in PR Dashboard Jul 6, 2026
@kpodemski kpodemski added waiting for author Waiting for author's feedback and removed Waiting for QA Status: Action required, Waiting for test feedback labels Jul 7, 2026
@kpodemski

Copy link
Copy Markdown
Contributor

Hello @boo-code

CI is red.

PHPUnit 5.7.27 by Sebastian Bergmann and contributors.

.E.....................................EE......................  63 / 101 ( 62%)
......................................                          101 / 101 (100%)

Time: 194 ms, Memory: 16.00MB

There were 3 errors:

1) PrestaShop\Module\FacetedSearch\Tests\Adapter\MySQLTest::testGetQueryAppliesOutOfStockLastOnSearchRelevanceOrdering
Error: Call to undefined method PrestaShop\Module\FacetedSearch\Tests\Adapter\MySQLTest::assertStringContainsString()

/home/runner/work/ps_facetedsearch/ps_facetedsearch/tests/php/FacetedSearch/Adapter/MySQLTest.php:111
phpvfscomposer:///home/runner/work/ps_facetedsearch/ps_facetedsearch/vendor/phpunit/phpunit/phpunit:51

2) PrestaShop\Module\FacetedSearch\Tests\Adapter\MySQLTest::testGetQueryWithComputeShowLastEnabled
Mockery\Exception\RuntimeException: Could not load mock Product, class already exists

/home/runner/work/ps_facetedsearch/ps_facetedsearch/vendor/mockery/mockery/library/Mockery/Container.php:218
/home/runner/work/ps_facetedsearch/ps_facetedsearch/vendor/mockery/mockery/library/Mockery.php:171
/home/runner/work/ps_facetedsearch/ps_facetedsearch/tests/php/FacetedSearch/Adapter/MySQLTest.php:455
phpvfscomposer:///home/runner/work/ps_facetedsearch/ps_facetedsearch/vendor/phpunit/phpunit/phpunit:51

3) PrestaShop\Module\FacetedSearch\Tests\Adapter\MySQLTest::testGetQueryWithComputeShowLastEnabledAndDenyOrderOutOfStockProducts
Mockery\Exception\RuntimeException: Could not load mock Product, class already exists

/home/runner/work/ps_facetedsearch/ps_facetedsearch/vendor/mockery/mockery/library/Mockery/Container.php:218
/home/runner/work/ps_facetedsearch/ps_facetedsearch/vendor/mockery/mockery/library/Mockery.php:171
/home/runner/work/ps_facetedsearch/ps_facetedsearch/tests/php/FacetedSearch/Adapter/MySQLTest.php:479
phpvfscomposer:///home/runner/work/ps_facetedsearch/ps_facetedsearch/vendor/phpunit/phpunit/phpunit:51

ERRORS!
Tests: 101, Assertions: 917, Errors: 3.
Script @php -d date.timezone=UTC ./vendor/bin/phpunit -c tests/php/phpunit.xml handling the test event returned with error code 2

The new test used assertStringContainsString(), which does not exist in the
PHPUnit 5.7 the module runs on, and mocked Product with Mockery::mock() plus a
MockProxy Product stub. That stub declared a global Product class at bootstrap,
so the existing computeShowLast tests could no longer create their Product mock
with Mockery::namedMock() (class already exists).

Drop the MockProxy Product stub, mock Product through namedMock() like the
neighbouring tests, and assert with assertContains() so the suite is green
again on PHPUnit 5.7.
@boo-code

boo-code commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Thanks. Fixed in 1d00b91. The new test used assertStringContainsString() (not available in PHPUnit 5.7) and declared a global Product stub in MockProxy, which stopped the existing computeShowLast tests from creating their Mockery::namedMock(Product::class) (class already exists). It now mocks Product through namedMock() like the neighbouring tests and asserts with assertContains(). PHPUnit is green again.

@Hlavtox Hlavtox left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hello @boo-code, I have to disagree on this one, because I think it's the expected behavior.

Let me explain. When you search for anything, you would still expect the most relevant thing to be in the front, even if it's not in stock.

@boo-code

boo-code commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @Hlavtox, that is a fair point and I am happy to defer to your call on the module's direction. Two clarifications on what the PR actually does, in case they shift the picture:

  • It only reorders when the merchant has explicitly enabled PS_LAYERED_FILTER_SHOW_OUT_OF_STOCK_LAST. With that setting off, search is untouched and stays pure relevance.
  • Even with it on, relevance is preserved within each stock group: the FIELD(p.id_product, ...) relevance order is kept and applied after the in-stock / out-of-stock split, so the most relevant in-stock result still leads and a highly relevant out-of-stock item only drops below the in-stock ones rather than losing its relative order.

The intent was consistency: category, manufacturer and other listings already route through computeShowLast and honour that setting, and search was the one surface that returned early and skipped it. But if you would still prefer search to stay relevance-first regardless of the setting, I am glad to close this.

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

Labels

waiting for author Waiting for author's feedback

Projects

Status: To be tested

Development

Successfully merging this pull request may close these issues.

Search relevance ordering bypasses out-of-stock-last logic

4 participants