From ea2ec6816e5560c02d776b878df4681dced21f63 Mon Sep 17 00:00:00 2001 From: "o.kravchuk" Date: Mon, 28 Jul 2025 18:32:50 +0300 Subject: [PATCH 1/3] magento/magento2#40104. Refactoring. Add tests to check single-option-price issue. --- .../Model/ConfigurableMaxPriceCalculator.php | 4 ++-- .../Price/ConfigurableRegularPrice.php | 17 +++++++------ .../ConfigurableViewOnCategoryPageTest.php | 11 +++++++++ ..._product_with_one_simple_with_category.php | 24 +++++++++++++++++++ ...with_one_simple_with_category_rollback.php | 11 +++++++++ 5 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php create mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category_rollback.php diff --git a/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php b/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php index 53496206963df..1f07a0271dcda 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php +++ b/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php @@ -49,7 +49,7 @@ public function __construct( * @param int $productId * @return float */ - public function getMaxPriceForConfigurableProduct($productId) + public function getMaxPriceForConfigurableProduct(int $productId): float { $connection = $this->resourceConnection->getConnection(); $superLinkTable = $this->resourceConnection->getTableName('catalog_product_super_link'); @@ -63,7 +63,7 @@ public function getMaxPriceForConfigurableProduct($productId) $result = $connection->fetchRow($select); if ($result && isset($result['max_price'])) { - return $result['max_price']; + return (float)$result['max_price']; } // Return a default value or handle the case where there's no max price diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php index 58c5e422a73a5..b2479a540ee9e 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php @@ -196,7 +196,7 @@ public function _resetState(): void } /** - * Check whether Configurable Product have more than one children products + * Check whether Configurable options do not have price difference * * @param SaleableInterface $product * @return bool @@ -204,18 +204,17 @@ public function _resetState(): void public function isChildProductsOfEqualPrices(SaleableInterface $product): bool { $minPrice = $this->getMinRegularAmount()->getValue(); - $final_price = $product->getFinalPrice(); - $productId = $product->getId(); - if ($final_price < $minPrice) { + $finalPrice = $product->getFinalPrice(); + $productId = (int)$product->getId(); + if ($finalPrice < $minPrice) { return false; } - $attributes = $product->getTypeInstance()->getConfigurableAttributes($product); - $items = $attributes->getItems(); - $options = reset($items); + $maxPrice = $this->configurableMaxPriceCalculator->getMaxPriceForConfigurableProduct($productId); - if ($maxPrice == 0) { + if ($maxPrice === 0.0) { $maxPrice = $this->getMaxRegularAmount()->getValue(); } - return (count($options->getOptions()) > 1) && $minPrice == $maxPrice; + + return $minPrice === $maxPrice; } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php index cf8bfd31387fe..1b8b12dc4089f 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php @@ -129,6 +129,17 @@ public function testCheckConfigurablePriceOnSecondWebsite(): void $this->assertProductPrice('configurable', '$150.00'); } + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php + * + * @return void + */ + public function testCheckConfigurablePriceOnOneSimple(): void + { + $this->resetPageLayout(); + $this->assertProductPrice('configurable', '$10.00'); + } + /** * @param string $sku * @param string $priceString diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php new file mode 100644 index 0000000000000..9c0275d26c429 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php @@ -0,0 +1,24 @@ +requireDataFixture('Magento/Catalog/_files/category.php'); +Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/configurable_product_with_one_simple.php'); + +$objectManager = Bootstrap::getObjectManager(); +/** @var CategoryLinkManagementInterface $categoryLinkManagement */ +$categoryLinkManagement = $objectManager->create(CategoryLinkManagementInterface::class); +/** @var DefaultCategory $categoryHelper */ +$categoryHelper = $objectManager->get(DefaultCategory::class); + +foreach (['simple_1', 'configurable'] as $sku) { + $categoryLinkManagement->assignProductToCategories($sku, [$categoryHelper->getId(), 333]); +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category_rollback.php new file mode 100644 index 0000000000000..dc8c1eab28c3e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category_rollback.php @@ -0,0 +1,11 @@ +requireDataFixture('Magento/Catalog/_files/category_rollback.php'); +Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php'); From c8812e70d815a48fe8e206c29f49d9d2c95b6b59 Mon Sep 17 00:00:00 2001 From: "o.kravchuk" Date: Tue, 29 Jul 2025 19:24:45 +0300 Subject: [PATCH 2/3] magento/magento2#40104. Refactoring. Simplify original logic. Fix static tests. --- .../Model/ConfigurableMaxPriceCalculator.php | 4 +- .../Price/ConfigurableRegularPrice.php | 25 +++++++-- .../templates/product/price/final_price.phtml | 7 +-- .../ConfigurableViewOnCategoryPageTest.php | 56 ++++++++++++++++--- ..._product_with_one_simple_with_category.php | 24 -------- ...with_one_simple_with_category_rollback.php | 11 ---- 6 files changed, 72 insertions(+), 55 deletions(-) delete mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php delete mode 100644 dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category_rollback.php diff --git a/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php b/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php index 1f07a0271dcda..b11ab0f9592b4 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php +++ b/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php @@ -1,7 +1,7 @@ isPriceEqualAcrossChildProducts(); + } + + /** + * Check whether Configurable options do not have price difference + * + * @return bool + */ + public function isPriceEqualAcrossChildProducts(): bool { $minPrice = $this->getMinRegularAmount()->getValue(); - $finalPrice = $product->getFinalPrice(); - $productId = (int)$product->getId(); - if ($finalPrice < $minPrice) { + + if ($this->product instanceof Product && $this->product->getFinalPrice() < $minPrice) { return false; } - $maxPrice = $this->configurableMaxPriceCalculator->getMaxPriceForConfigurableProduct($productId); + $maxPrice = $this->configurableMaxPriceCalculator + ->getMaxPriceForConfigurableProduct((int)$this->product->getId()); if ($maxPrice === 0.0) { $maxPrice = $this->getMaxRegularAmount()->getValue(); } diff --git a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml index 880a11fa9b305..792dba8137511 100644 --- a/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml +++ b/app/code/Magento/ConfigurableProduct/view/base/templates/product/price/final_price.phtml @@ -1,7 +1,7 @@ getPriceType('final_price'); $regularPriceModel = $block->getPriceType('regular_price'); $idSuffix = $block->getIdSuffix() ? $block->getIdSuffix() : ''; $schema = ($block->getZone() == 'item_view') ? true : false; -$product = $regularPriceModel->getProduct(); ?> renderAmount($finalPriceModel->getAmount(), [ - 'display_label' => $regularPriceModel->isChildProductsOfEqualPrices($product) ? '' : __('As low as'), + 'display_label' => $regularPriceModel->isPriceEqualAcrossChildProducts() ? '' : __('As low as'), 'price_id' => $block->getPriceId('product-price-' . $idSuffix), 'price_type' => 'finalPrice', 'include_container' => true, diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php index 1b8b12dc4089f..435b642013b67 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Block/Product/View/Type/ConfigurableViewOnCategoryPageTest.php @@ -1,7 +1,7 @@ assertProductPrice('configurable', '$150.00'); } - /** - * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php - * - * @return void - */ + #[ + AppIsolation(true), + DataFixture(CategoryFixture::class, [], 'category'), + DataFixture(ProductFixture::class, [ + 'sku' => 'simple_1', + 'price' => 10.0, + 'visibility' => Visibility::VISIBILITY_NOT_VISIBLE + ], 'p1'), + DataFixture(AttributeFixture::class, as: 'attr'), + DataFixture( + ConfigurableProductFixture::class, + [ + 'sku' => 'configurable', + '_options' => ['$attr$'], + '_links' => ['$p1$'] + ], + 'configurable' + ), + DataFixture( + AssignProductsFixture::class, + ['products' => ['$configurable$', '$p1$'], 'category' => '$category$'], + as: 'assignProducts' + ) + ] public function testCheckConfigurablePriceOnOneSimple(): void { $this->resetPageLayout(); - $this->assertProductPrice('configurable', '$10.00'); + $fixtures = DataFixtureStorageManager::getStorage(); + + $this->registry->unregister('current_category'); + $this->registry->register( + 'current_category', + $fixtures->get('category') + ); + $this->page->addHandle(['default', 'catalog_category_view']); + $this->page->getLayout()->generateXml(); + + $this->assertCollectionSize(1, $this->getListingBlock()->getLoadedProductCollection()); + $priceHtml = $this->getListingBlock()->getProductPrice($this->getProduct('configurable')); + $this->assertStringContainsString('$10.00', $this->clearPriceHtml($priceHtml)); } /** diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php deleted file mode 100644 index 9c0275d26c429..0000000000000 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php +++ /dev/null @@ -1,24 +0,0 @@ -requireDataFixture('Magento/Catalog/_files/category.php'); -Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/configurable_product_with_one_simple.php'); - -$objectManager = Bootstrap::getObjectManager(); -/** @var CategoryLinkManagementInterface $categoryLinkManagement */ -$categoryLinkManagement = $objectManager->create(CategoryLinkManagementInterface::class); -/** @var DefaultCategory $categoryHelper */ -$categoryHelper = $objectManager->get(DefaultCategory::class); - -foreach (['simple_1', 'configurable'] as $sku) { - $categoryLinkManagement->assignProductToCategories($sku, [$categoryHelper->getId(), 333]); -} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category_rollback.php deleted file mode 100644 index dc8c1eab28c3e..0000000000000 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category_rollback.php +++ /dev/null @@ -1,11 +0,0 @@ -requireDataFixture('Magento/Catalog/_files/category_rollback.php'); -Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/configurable_product_with_one_simple_with_category.php'); From e04de5504001f80bd15784b1a27c2b27d74366da Mon Sep 17 00:00:00 2001 From: "o.kravchuk" Date: Wed, 30 Jul 2025 10:54:48 +0300 Subject: [PATCH 3/3] magento/magento2#40104. Fix code styles. --- .../Model/ConfigurableMaxPriceCalculator.php | 2 +- .../Pricing/Price/ConfigurableRegularPrice.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php b/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php index b11ab0f9592b4..fc4411f29a006 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php +++ b/app/code/Magento/ConfigurableProduct/Model/ConfigurableMaxPriceCalculator.php @@ -63,7 +63,7 @@ public function getMaxPriceForConfigurableProduct(int $productId): float $result = $connection->fetchRow($select); if ($result && isset($result['max_price'])) { - return (float)$result['max_price']; + return (float) $result['max_price']; } // Return a default value or handle the case where there's no max price diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php index 64455d1cefd3d..acb50b8b9fbdd 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php @@ -223,7 +223,7 @@ public function isPriceEqualAcrossChildProducts(): bool } $maxPrice = $this->configurableMaxPriceCalculator - ->getMaxPriceForConfigurableProduct((int)$this->product->getId()); + ->getMaxPriceForConfigurableProduct((int) $this->product->getId()); if ($maxPrice === 0.0) { $maxPrice = $this->getMaxRegularAmount()->getValue(); }