diff --git a/CHANGELOG-7.2.md b/CHANGELOG-7.2.md index a6de2942e8..ac14102293 100644 --- a/CHANGELOG-7.2.md +++ b/CHANGELOG-7.2.md @@ -3,7 +3,7 @@ ## v7.2.0 - unreleased ### Added -- +- Set custom product low stock label [#0004401](https://bugs.oxid-esales.com/view.php?id=4401) ### Deprecated - diff --git a/source/Application/Model/Article.php b/source/Application/Model/Article.php index 40f8c837a6..2c1c9057f8 100644 --- a/source/Application/Model/Article.php +++ b/source/Application/Model/Article.php @@ -164,9 +164,11 @@ class Article extends \OxidEsales\Eshop\Core\Model\MultiLanguageModel implements /** * $_fPricePerUnit holds price per unit value in active shop currency. - * $_fPricePerUnit is calculated from \OxidEsales\Eshop\Application\Model\Article::oxarticles__oxunitquantity->value - * and from \OxidEsales\Eshop\Application\Model\Article::oxarticles__oxuniname->value. If either one of these values is empty then $_fPricePerUnit is not calculated. - * Example: In case when product price is 10 EUR and product quantity is 0.5 (liters) then $_fPricePerUnit would be 20,00 + * $_fPricePerUnit is calculated from + * \OxidEsales\Eshop\Application\Model\Article::oxarticles__oxunitquantity->value + * and from \OxidEsales\Eshop\Application\Model\Article::oxarticles__oxuniname->value. If either one of these + * values is empty then $_fPricePerUnit is not calculated. Example: In case when product price is 10 EUR and + * product quantity is 0.5 (liters) then $_fPricePerUnit would be 20,00 */ protected $_fPricePerUnit = null; @@ -469,7 +471,8 @@ class Article extends \OxidEsales\Eshop\Core\Model\MultiLanguageModel implements * Constructor, sets shop ID for article (\OxidEsales\Eshop\Core\Config::getShopId()), * initiates parent constructor (parent::oxI18n()). * - * @param array $aParams The array of names and values of oxArticle instance properties to be set on object instantiation + * @param array $aParams The array of names and values of oxArticle instance properties to be set on object + * instantiation */ public function __construct($aParams = null) { @@ -709,7 +712,8 @@ protected function createSqlActiveSnippet($forceCoreTable) } /** - * Assign condition setter. In case article assignment is skipped ($_blSkipAssign = true), it does not perform additional + * Assign condition setter. In case article assignment is skipped ($_blSkipAssign = true), it does not perform + * additional * * @param bool $blSkipAssign Whether to skip assign process for the article */ @@ -1522,7 +1526,8 @@ public function hasDownloadableAgreement() * * @param array $aFilterIds ids of active selections [optional] * @param string $sActVariantId active variant id [optional] - * @param int $iLimit limit variant lists count (if non zero, return limited number of multidimensional variant selections) + * @param int $iLimit limit variant lists count (if non zero, return limited number of multidimensional + * variant selections) * * @return array */ @@ -1613,7 +1618,8 @@ public function getSelections($iLimit = null, $aFilter = null) /** * Returns variant list (list contains oxArticle objects) * - * @param bool $blRemoveNotOrderables if true, removes from list not orderable articles, which are out of stock [optional] + * @param bool $blRemoveNotOrderables if true, removes from list not orderable articles, which are out of stock + * [optional] * @param bool $blForceCoreTable if true forces core table use, default is false [optional] * * @return \OxidEsales\Eshop\Application\Model\ArticleList @@ -1625,7 +1631,8 @@ public function getFullVariants($blRemoveNotOrderables = true, $blForceCoreTable /** * Collects and returns article variants. - * Note: Only active variants are returned by this method. If you need full variant list use \OxidEsales\Eshop\Application\Model\Article::getAdminVariants() + * Note: Only active variants are returned by this method. If you need full variant list use + * \OxidEsales\Eshop\Application\Model\Article::getAdminVariants() * * @param bool $blRemoveNotOrderables if true, removes from list not orderable articles, which are out of stock * @param bool $blForceCoreTable if true forces core table use, default is false [optional] @@ -2406,7 +2413,8 @@ public function getPictureGallery() * This function is triggered whenever article is saved or deleted or after the stock is changed. * Originally we need to update the oxstock for possible article parent in case parent is not buyable * Plus you may want to extend this function to update some extended information. - * Call \OxidEsales\Eshop\Application\Model\Article::onChange($sAction, $sOXID) with ID parameter when changes are executed over SQL. + * Call \OxidEsales\Eshop\Application\Model\Article::onChange($sAction, $sOXID) with ID parameter when changes are + * executed over SQL. * (or use module class instead of oxArticle if such exists) * * @param string $action Action constant @@ -3549,8 +3557,10 @@ public function hasAmountPrice() /** * Loads and returns variants list. * - * @param bool $loadSimpleVariants if parameter $blSimple - list will be filled with oxSimpleVariant objects, else - oxArticle - * @param bool $blRemoveNotOrderables if true, removes from list not orderable articles, which are out of stock [optional] + * @param bool $loadSimpleVariants if parameter $blSimple - list will be filled with oxSimpleVariant + * objects, else - oxArticle + * @param bool $blRemoveNotOrderables if true, removes from list not orderable articles, which are out of + * stock [optional] * @param bool|null $forceCoreTableUsage if true forces core table use, default is false [optional] * * @return array|\OxidEsales\Eshop\Application\Model\SimpleVariantList|\OxidEsales\Eshop\Application\Model\ArticleList @@ -3697,7 +3707,8 @@ protected function getActiveCategorySelectSnippet() * Calculates price of article (adds taxes, currency and discounts). * * @param \OxidEsales\Eshop\Core\Price $oPrice price object - * @param double $dVat vat value, optional, if passed, bypasses "bl_perfCalcVatOnlyForBasketOrder" config value + * @param double $dVat vat value, optional, if passed, bypasses + * "bl_perfCalcVatOnlyForBasketOrder" config value * * @return \OxidEsales\Eshop\Core\Price */ @@ -4482,18 +4493,14 @@ protected function assignStock() $this->oxarticles__oxstockflag->value != 4 ) { //ORANGE light - $iStock = $this->oxarticles__oxstock->value; - - if ($this->_blNotBuyableParent) { - $iStock = $this->oxarticles__oxvarstock->value; - } + $stock = $this->getAvailableStock(); - if ($iStock <= $myConfig->getConfigParam('sStockWarningLimit') && $iStock > 0) { + if ($stock > 0 && $this->isLowStock()) { $this->_iStockStatus = 1; } //RED light - if ($iStock <= 0) { + if ($stock <= 0) { $this->_iStockStatus = -1; } } @@ -5255,7 +5262,8 @@ protected function getLoadVariantsQuery($blRemoveNotOrderables, $forceCoreTableU * Set needed parameters to article list object like language. * * @param \OxidEsales\Eshop\Core\Model\BaseModel $baseObject article list template object. - * @param bool|null $forceCoreTableUsage if true forces core table use, default is false [optional] + * @param bool|null $forceCoreTableUsage if true forces core table use, default is + * false [optional] */ protected function updateVariantsBaseObject($baseObject, $forceCoreTableUsage = null) { @@ -5269,4 +5277,23 @@ protected function updateManufacturerBeforeLoading($oManufacturer) { $oManufacturer->setReadOnly(true); } + + private function getAvailableStock(): int + { + return (int) ($this->_blNotBuyableParent + ? $this->oxarticles__oxvarstock->value + : $this->oxarticles__oxstock->value); + } + + private function isLowStock(): bool + { + return $this->getAvailableStock() <= $this->getLowStockThreshold(); + } + + private function getLowStockThreshold(): int + { + return (int) ($this->oxarticles__oxlowstockactive->value ? + $this->oxarticles__oxremindamount->value : + Registry::getConfig()->getConfigParam('sStockWarningLimit')); + } } diff --git a/source/Core/ViewConfig.php b/source/Core/ViewConfig.php index 414250b9fd..bb336f7ee3 100644 --- a/source/Core/ViewConfig.php +++ b/source/Core/ViewConfig.php @@ -10,6 +10,7 @@ use OxidEsales\Eshop\Core\Exception\FileException; use OxidEsales\Eshop\Core\Registry; use OxidEsales\Eshop\Core\Str; +use OxidEsales\EshopCommunity\Core\Exception\ShopException; use OxidEsales\EshopCommunity\Internal\Framework\Module\Path\ModuleAssetsPathResolverBridgeInterface; use OxidEsales\EshopCommunity\Internal\Framework\Module\Setup\Bridge\ModuleActivationBridgeInterface; use OxidEsales\Facts\Facts; @@ -984,26 +985,21 @@ public function getNavFormParams() return $sParams; } - /** - * Returns config param "blStockOnDefaultMessage" value - * - * @return string - */ public function getStockOnDefaultMessage() { return Registry::getConfig()->getConfigParam('blStockOnDefaultMessage'); } - /** - * Returns config param "blStockOnDefaultMessage" value - * - * @return string - */ public function getStockOffDefaultMessage() { return Registry::getConfig()->getConfigParam('blStockOffDefaultMessage'); } + public function getStockLowDefaultMessage(): bool + { + return (bool) Registry::getConfig()->getConfigParam('blStockLowDefaultMessage'); + } + /** * Returns shop version defined in view * diff --git a/source/Internal/Setup/Database/Sql/initial_data.sql b/source/Internal/Setup/Database/Sql/initial_data.sql index 2cf2be1927..8732e9e1bb 100644 --- a/source/Internal/Setup/Database/Sql/initial_data.sql +++ b/source/Internal/Setup/Database/Sql/initial_data.sql @@ -28,6 +28,7 @@ INSERT INTO `oxconfig` (`OXID`, `OXSHOPID`, `OXMODULE`, `OXVARNAME`, `OXVARTYPE` ('2a944b2cc31311e8957700163e4021bf', 1, '', 'includeProductReviewLinksInEmail', 'bool', ''), ('2ca4277aa49a5bd27.44511187', 1, '', 'blStockOnDefaultMessage', 'bool', '1'), ('2ca4277aa49a634f8.76432326', 1, '', 'blStockOffDefaultMessage', 'bool', '1'), +('0282a93ba014458d7a9249e5aef1a8eb', 1, '', 'blStockLowDefaultMessage', 'bool', '1'), ('2e4452b5763e03c74.88240349', 1, '', 'blDisableDublArtOnCopy', 'bool', '1'), ('32ddeaf2694e06b47b6ff74eafc69b65', 1, '', 'sParcelService', 'str', 'http://www.dpd.de/cgi-bin/delistrack?typ=1&lang=de&pknr=##ID##'), ('33341949f476b65e8.17282442', 1, '', 'iAttributesPercent', 'str', '70'), diff --git a/source/Setup/Sql/initial_data.sql b/source/Setup/Sql/initial_data.sql index f38b70343f..85d3cf0178 100644 --- a/source/Setup/Sql/initial_data.sql +++ b/source/Setup/Sql/initial_data.sql @@ -28,6 +28,7 @@ INSERT INTO `oxconfig` (`OXID`, `OXSHOPID`, `OXMODULE`, `OXVARNAME`, `OXVARTYPE` ('2a944b2cc31311e8957700163e4021bf', 1, '', 'includeProductReviewLinksInEmail', 'bool', ''), ('2ca4277aa49a5bd27.44511187', 1, '', 'blStockOnDefaultMessage', 'bool', '1'), ('2ca4277aa49a634f8.76432326', 1, '', 'blStockOffDefaultMessage', 'bool', '1'), +('0282a93ba014458d7a9249e5aef1a8eb', 1, '', 'blStockLowDefaultMessage', 'bool', '1'), ('2e4452b5763e03c74.88240349', 1, '', 'blDisableDublArtOnCopy', 'bool', '1'), ('32ddeaf2694e06b47b6ff74eafc69b65', 1, '', 'sParcelService', 'str', 'http://www.dpd.de/cgi-bin/delistrack?typ=1&lang=de&pknr=##ID##'), ('33341949f476b65e8.17282442', 1, '', 'iAttributesPercent', 'str', '70'), diff --git a/source/migration/data/Version20231128113123.php b/source/migration/data/Version20231128113123.php new file mode 100644 index 0000000000..3995458827 --- /dev/null +++ b/source/migration/data/Version20231128113123.php @@ -0,0 +1,49 @@ +addSql( + 'ALTER TABLE `oxarticles` ADD column `OXLOWSTOCKTEXT` VARCHAR(255) NOT NULL default "" COMMENT ' + . '"Message, which is shown if the article is in low stock (multilanguage)" AFTER `OXSTOCKTEXT`' + ); + $this->addSql( + 'ALTER TABLE `oxarticles` ADD column `OXLOWSTOCKTEXT_1` VARCHAR(255) NOT NULL default "" AFTER ' + . '`OXSTOCKTEXT_3`' + ); + $this->addSql( + 'ALTER TABLE `oxarticles` ADD column `OXLOWSTOCKTEXT_2` VARCHAR(255) NOT NULL default "" AFTER ' + . '`OXLOWSTOCKTEXT_1`' + ); + $this->addSql( + 'ALTER TABLE `oxarticles` ADD column `OXLOWSTOCKTEXT_3` VARCHAR(255) NOT NULL default "" AFTER ' + . '`OXLOWSTOCKTEXT_2`' + ); + $this->addSql( + 'ALTER TABLE `oxarticles` ADD column `OXLOWSTOCKACTIVE` TINYINT(1) AFTER ' + . '`OXLOWSTOCKTEXT`' + ); + } + + public function down(Schema $schema): void + { + } +} diff --git a/tests/Codeception/Acceptance/Admin/MasterCoreStockSettingsCest.php b/tests/Codeception/Acceptance/Admin/MasterCoreStockSettingsCest.php new file mode 100644 index 0000000000..e59025d00c --- /dev/null +++ b/tests/Codeception/Acceptance/Admin/MasterCoreStockSettingsCest.php @@ -0,0 +1,40 @@ +wantToTest('Activate and deactivate default stock message'); + + $adminPanel = $I->loginAdmin(); + $coreSettings = $adminPanel->openCoreSettings(); + $settingsTab = $coreSettings->openSettingsTab(); + + $I->amGoingTo('Check Low stock default message option'); + $stockDropdown = $settingsTab->openStockSettings(); + $stockDropdown->checkLowStockMessageOption(); + $settingsTab->save(); + + $stockDropdown->seeLowStockMessageSelected(); + + $I->amGoingTo('Uncheck Low stock default message option'); + $stockDropdown = $settingsTab->openStockSettings(); + $stockDropdown->uncheckLowStockMessageOption(); + $settingsTab->save(); + + $stockDropdown->dontSeeLowStockMessageSelected(); + } +} diff --git a/tests/Codeception/Acceptance/Admin/ProductStockTestCest.php b/tests/Codeception/Acceptance/Admin/ProductStockTestCest.php new file mode 100644 index 0000000000..e69d7b956b --- /dev/null +++ b/tests/Codeception/Acceptance/Admin/ProductStockTestCest.php @@ -0,0 +1,48 @@ +wantToTest('Set low stock message for product'); + + $productsMainPage = $I->loginAdmin()->openProducts(); + $productMainTab = $productsMainPage->find($productsMainPage->searchNumberInput, $this->productID); + $stockTab = $productMainTab->openStockTab(); + $lowStockMessage = 'This product is in low stock' . $this->productID; + $remindAmount = 20.5; + + $I->amGoingTo('Set and activate low stock message'); + + $stockTab->checkLowStockMessageOption() + ->setRemindAmountValue($remindAmount) + ->setLowStockMessageValue($lowStockMessage) + ->save(); + + $stockTab->seeRemindAmountValue($remindAmount); + $stockTab->seeLowStockMessageSelected(); + $stockTab->seeLowStockMessageValue($lowStockMessage); + + $I->amGoingTo('Disable low stock message'); + + $stockTab->uncheckLowStockMessageOption() + ->save(); + + $stockTab->dontSeeLowStockMessageSelected(); + } +} diff --git a/tests/Codeception/Acceptance/ProductDetailsPageCest.php b/tests/Codeception/Acceptance/ProductDetailsPageCest.php index 2612e6b77f..45e249b2d9 100644 --- a/tests/Codeception/Acceptance/ProductDetailsPageCest.php +++ b/tests/Codeception/Acceptance/ProductDetailsPageCest.php @@ -9,18 +9,19 @@ namespace OxidEsales\EshopCommunity\Tests\Codeception\Acceptance; +use Codeception\Attribute\Group; use Codeception\Util\Fixtures; use OxidEsales\Codeception\Module\Translation\Translator; use OxidEsales\Codeception\Step\ProductNavigation; use OxidEsales\EshopCommunity\Tests\Codeception\Support\AcceptanceTester; +#[Group('product')] final class ProductDetailsPageCest { - /** - * @group main - * @group product - * @group productVariants - */ + private string $productId = '1000'; + private string $productVariantId = '1001432'; + + #[Group('main', 'productVariants')] public function selectMultidimensionalVariantsInDetailsPage(AcceptanceTester $I): void { $productNavigation = new ProductNavigation($I); @@ -117,10 +118,7 @@ public function selectMultidimensionalVariantsInDetailsPage(AcceptanceTester $I) $detailsPage->seeMiniBasketContains([$basketItem], '50,00 €', '2'); } - /** - * @group product - * @group search - */ + #[Group('search')] public function navigateInDetailsPage(AcceptanceTester $I): void { $I->wantToTest('product navigation in details page'); @@ -156,9 +154,6 @@ public function navigateInDetailsPage(AcceptanceTester $I): void $detailsPage->seeOnBreadCrumb($breadCrumb); } - /** - * @group product - */ public function detailsPageInformation(AcceptanceTester $I): void { $I->wantToTest('product information in details page'); @@ -200,10 +195,7 @@ public function detailsPageInformation(AcceptanceTester $I): void ->seeAttributeValue('attr value 12 [EN] šÄßüл', 3); } - /** - * @group product - * @group productVariants - */ + #[Group('productVariants')] public function selectProductVariant(AcceptanceTester $I): void { $productNavigation = new ProductNavigation($I); @@ -300,10 +292,7 @@ public function selectProductVariantsWithSelectionLists(AcceptanceTester $I): vo ->seeBasketContainsSelectionList($selectionListsTitle, $selectionList3Value, 2); } - /** - * @group product - * @group accessories - */ + #[Group('accessories')] public function checkProductAccessories(AcceptanceTester $I): void { $productNavigation = new ProductNavigation($I); @@ -335,10 +324,7 @@ public function checkProductAccessories(AcceptanceTester $I): void ->seeProductData($accessoryData); } - /** - * @group product - * @group similarProducts - */ + #[Group('similarProducts')] public function checkSimilarProducts(AcceptanceTester $I): void { $productNavigation = new ProductNavigation($I); @@ -372,10 +358,7 @@ public function checkSimilarProducts(AcceptanceTester $I): void ->seeSimilarProductData($productData, 1); } - /** - * @group product - * @group crossSelling - */ + #[Group('crossSelling')] public function checkProductCrossSelling(AcceptanceTester $I): void { $productNavigation = new ProductNavigation($I); @@ -408,10 +391,7 @@ public function checkProductCrossSelling(AcceptanceTester $I): void ->seeProductData($crossSellingProductData); } - /** - * @group product - * @group productPrice - */ + #[Group('productPrice')] public function checkProductPriceA(AcceptanceTester $I): void { $I->wantToTest('product price A'); @@ -483,10 +463,7 @@ public function checkProductPriceA(AcceptanceTester $I): void $I->clearShopCache(); } - /** - * @group product - * @group productPrice - */ + #[Group('productPrice')] public function checkProductPriceC(AcceptanceTester $I): void { $I->wantToTest('product price C and amount price discount added to this price'); @@ -541,10 +518,7 @@ public function checkProductPriceC(AcceptanceTester $I): void $I->clearShopCache(); } - /** - * @group product - * @group productPrice - */ + #[Group('productPrice')] public function checkProductPriceB(AcceptanceTester $I): void { $I->wantToTest('product price B'); @@ -590,11 +564,7 @@ public function checkProductPriceB(AcceptanceTester $I): void $I->clearShopCache(); } - /** - * @group product - * @group productPrice - * @group productAmountPrice - */ + #[Group('productPrice', 'productAmountPrice')] public function checkProductAmountPrice(AcceptanceTester $I): void { $productNavigation = new ProductNavigation($I); @@ -627,6 +597,118 @@ public function checkProductAmountPrice(AcceptanceTester $I): void ->seeAmountPrices($amountPrices); } + #[Group('stock')] + public function lowStockProductTests(AcceptanceTester $I): void + { + $I->wantToTest('product low stock label'); + + $I->updateInDatabase( + 'oxarticles', + [ + 'OXSTOCK' => 1 + ], + [ + 'OXID' => $this->productId + ] + ); + + $productListPage = $I->openShop()->searchFor($this->productId); + $productListPage->openProductDetailsPage(1); + + $I->see(Translator::translate('LOW_STOCK')); + + + $I->amGoingTo('Test product low stock label with deactivated default option'); + + $I->updateConfigInDatabase('blStockLowDefaultMessage', '0', 'bool'); + $I->updateInDatabase( + 'oxarticles', + [ + 'OXLOWSTOCKACTIVE' => 0 + ], + [ + 'OXID' => $this->productId + ] + ); + $I->reloadPage(); + $product = $this->getProductData($this->productId); + + $I->dontSee($product['OXSTOCKTEXT_1']); + + + $I->amGoingTo('Test product low stock label with product flag enabled'); + + $I->updateConfigInDatabase('blStockLowDefaultMessage', '1', 'bool'); + $lowStockMessage = 'product has low stock'; + $I->updateInDatabase( + 'oxarticles', + [ + 'OXREMINDAMOUNT' => 20, + 'OXLOWSTOCKTEXT_1' => $lowStockMessage, + 'OXLOWSTOCKACTIVE' => 1 + ], + [ + 'OXID' => $this->productId + ] + ); + $I->reloadPage(); + + $I->see($lowStockMessage); + } + + #[Group('stock', 'productVariants')] + public function lowStockProductVariantTests(AcceptanceTester $I): void + { + $I->wantToTest('product variant low stock label'); + + $productVariant = $this->getProductData($this->productVariantId); + + $productListPage = $I->openShop()->searchFor($productVariant['OXPARENTID']); + $detailsPage = $productListPage->openProductDetailsPage(1); + + $I->dontSee(Translator::translate('LOW_STOCK')); + + $I->amGoingTo('Test product low stock label with deactivated default option'); + + $detailsPage->selectVariant(1, $productVariant['OXVARSELECT_1']); + $I->updateInDatabase( + 'oxarticles', + [ + 'OXLOWSTOCKACTIVE' => 0 + ], + [ + 'OXID' => $this->productVariantId + ] + ); + $I->see(Translator::translate('LOW_STOCK')); + + + $I->amGoingTo('Test product low stock label with product flag enabled'); + + $I->updateConfigInDatabase('blStockLowDefaultMessage', '1', 'bool'); + $lowStockMessage = 'product has low stock'; + $I->updateInDatabase( + 'oxarticles', + [ + 'OXREMINDAMOUNT' => 20, + 'OXLOWSTOCKTEXT_1' => $lowStockMessage, + 'OXLOWSTOCKACTIVE' => 1 + ], + [ + 'OXID' => $this->productVariantId + ] + ); + $I->reloadPage(); + $detailsPage->selectVariant(1, $productVariant['OXVARSELECT_1']); + + $I->see($lowStockMessage); + } + + private function getProductData(string $productID): array + { + return Fixtures::get('product-' . $productID); + } + private function getExistingUserData() { return Fixtures::get('existingUser'); diff --git a/tests/Codeception/Support/Data/dump.sql b/tests/Codeception/Support/Data/dump.sql index fa96a60575..cca522d5a5 100644 --- a/tests/Codeception/Support/Data/dump.sql +++ b/tests/Codeception/Support/Data/dump.sql @@ -6,7 +6,7 @@ REPLACE INTO `oxarticles` (`OXID`, `OXSHOPID`, `OXPARENTID`, `OXACTIVE`, `OX ( '1002', 1, '', 1, '1002', '[DE 2] Test product 2 šÄßüл', 'Test product 2 short desc [DE]', 55, 0, 0, 0, 0, '', 0, NULL, 0, 0, 1, 'In stock [DE]', 'Out of stock [DE]', '0000-00-00', '2008-02-04', '2008-02-04 17:18:18', 0, 0, 0, 'search1002', 1, 'variants [DE]', 10, 2, '', 55, 67, 'variants [EN] šÄßüл', '', 'Test product 2 [EN] šÄßüл', 'Test product 2 short desc [EN] šÄßüл', 'šÄßüл1002', '', 'In stock [EN] šÄßüл', 'Out of stock [EN] šÄßüл', 0, 'testdistributor', 'testmanufacturer', 1, 1, 'MONTH'), ('1002-1', 1, '1002', 1, '1002-1', '', '', 55, 45, 0, 0, 0, '', 0, NULL, 0, 5, 1, 'In stock [DE]', 'Out of stock [DE]', '0000-00-00', '2008-02-04', '2008-02-04 17:34:10', 0, 0, 0, '', 1, '', 0, 0, 'var1 [DE]', 0, 0, '', 'var1 [EN] šÄßüл', '', '', '', '', 'In stock [EN] šÄßüл', 'Out of stock [EN] šÄßüл', 1, '', '', 0, 0, ''), ('1002-2', 1, '1002', 1, '1002-2', '', '', 67, 47, 0, 0, 0, '', 0, NULL, 0, 5, 1, 'In stock [DE]', 'Out of stock [DE]', '0000-00-00', '2008-02-04', '2008-02-04 17:34:36', 0, 0, 0, '', 1, '', 0, 0, 'var2 [DE]', 0, 0, '', 'var2 [EN] šÄßüл', '', '', '', '', 'In stock [EN] šÄßüл', 'Out of stock [EN] šÄßüл', 2, '', '', 0, 0, ''), - ( '1001', 1, '', 1, '1001', '[DE 1] Test product 1 šÄßüл', 'Test product 1 short desc [DE]', 100, 0, 0, 0, 150, '', 0, 10, 0, 0, 1, '', '', '2030-01-01', '2008-02-04', '2008-02-04 17:35:49', 0, 0, 0, 'search1001', 1, '', 0, 0, '', 100, 0, '', '', 'Test product 1 [EN] šÄßüл', 'Test product 1 short desc [EN] šÄßüл', 'šÄßüл1001', '', '', '', 0, 'testdistributor', 'testmanufacturer', 0, 1, 'WEEK'), + ( '1001', 1, '', 1, '1001', '[DE 1] Test product 1 šÄßüл', 'Test product 1 short desc [DE]', 100, 0, 0, 0, 150, '', 0, 10, 0, 0, 1, '', '', '2030-01-01', '2008-02-04', '2008-02-04 17:35:49', 0, 0, 0, 'search1001', 1, '', 0, 0, '', 100, 0, '', '', 'Test product 1 [EN] šÄßüл', 'Test product 1 short desc [EN] šÄßüл', 'šÄßüл1001', '', '', '', 0, 'testdistributor', 'testmanufacturer', 0, 1, 'WEEK'), ('10014', 1, '', 1, '10014', '13 DE product šÄßüл', '14 DE description', 1.6, 0, 0, 0, 0, '', 0, NULL, 0, 0, 1, '', '', '0000-00-00', '2008-04-03', '2008-04-03 12:50:20', 0, 0, 0, '', 1, 'size[DE] | color | type', 0, 12, '', 15, 25, 'size[EN] | color | type', '', '14 EN product šÄßüл', '13 EN description šÄßüл', '', '', '', '', 0, '', '', 0, 0, ''); REPLACE INTO `oxarticles` (`OXID`, `OXSHOPID`, `OXPARENTID`, `OXACTIVE`, `OXARTNUM`, `OXTITLE`, `OXSHORTDESC`, `OXPRICE`, `OXPRICEA`, `OXPRICEB`, `OXPRICEC`, `OXTPRICE`, `OXUNITNAME`, `OXUNITQUANTITY`, `OXVAT`, `OXWEIGHT`, `OXSTOCK`, `OXSTOCKFLAG`, `OXSTOCKTEXT`, `OXNOSTOCKTEXT`, `OXDELIVERY`, `OXINSERT`, `OXTIMESTAMP`, `OXLENGTH`, `OXWIDTH`, `OXHEIGHT`, `OXSEARCHKEYS`, `OXISSEARCH`, `OXVARNAME`, `OXVARSTOCK`, `OXVARCOUNT`, `OXVARSELECT`, `OXVARMINPRICE`, `OXVARMAXPRICE`, `OXVARNAME_1`, `OXVARSELECT_1`, `OXTITLE_1`, `OXSHORTDESC_1`, `OXSEARCHKEYS_1`, `OXBUNDLEID`, `OXSTOCKTEXT_1`, `OXNOSTOCKTEXT_1`, `OXSORT`, `OXVENDORID`, `OXMANUFACTURERID`, `OXMINDELTIME`, `OXMAXDELTIME`, `OXDELTIMEUNIT`,`OXACTIVEFROM`, `OXACTIVETO`) VALUES diff --git a/tests/Integration/Application/Model/ArticleTest.php b/tests/Integration/Application/Model/ArticleTest.php index 9399906c4a..fa3a1c073d 100644 --- a/tests/Integration/Application/Model/ArticleTest.php +++ b/tests/Integration/Application/Model/ArticleTest.php @@ -176,4 +176,78 @@ public static function visibilityTimeRangesDataProvider(): array 'With invalid From/to' => [$future, $past, false], ]; } + + public static function productLowStockDataProvider(): array + { + return [ + 'Product in low stock: Shop limit reached, Product limit undefined' => [5, 0.0, 10, false], + 'Product in low stock: Shop limit exceeded, Product limit ignored' => [11, 20.0, 10, true], + 'Product in low stock: Product limit reached' => [5, 10.0, 0, true] + ]; + } + + #[DataProvider('productLowStockDataProvider')] + public function testProductLowStock( + int $productStock, + float $productLowStockLimit, + int $shopLowStockLimit, + bool $productLowStockActive + ): void { + Registry::getConfig()->setConfigParam('blUseStock', true); + Registry::getConfig()->setConfigParam('sStockWarningLimit', $shopLowStockLimit); + + $product = oxNew(Article::class); + $product->assign([ + 'oxarticles__oxstock' => $productStock, + 'oxarticles__oxremindamount' => $productLowStockLimit, + 'oxarticles__oxlowstockactive' => $productLowStockActive, + 'oxarticles__oxparentid' => '', + 'oxarticles__oxstockflag' => 1, + 'oxarticles__oxshopid' => 1, + 'oxarticles__oxvarstock' => $productStock, + 'oxarticles__oxvarcount' => 0 + ]); + + $this->assertEquals(1, $product->getStockStatus()); + } + + public function testProductInStock(): void + { + Registry::getConfig()->setConfigParam('blUseStock', true); + Registry::getConfig()->setConfigParam('sStockWarningLimit', 3); + + $product = oxNew(Article::class); + $product->assign([ + 'oxarticles__oxstock' => 5, + 'oxarticles__oxremindamount' => 0.0, + 'oxarticles__oxlowstockactive' => false, + 'oxarticles__oxparentid' => '', + 'oxarticles__oxstockflag' => 1, + 'oxarticles__oxshopid' => 1, + 'oxarticles__oxvarstock' => 5, + 'oxarticles__oxvarcount' => 0 + ]); + + $this->assertEquals(0, $product->getStockStatus()); + } + + public function testProductOutStock(): void + { + Registry::getConfig()->setConfigParam('blUseStock', true); + Registry::getConfig()->setConfigParam('sStockWarningLimit', 0); + + $product = oxNew(Article::class); + $product->assign([ + 'oxarticles__oxstock' => -1, + 'oxarticles__oxremindamount' => 0.0, + 'oxarticles__oxlowstockactive' => false, + 'oxarticles__oxparentid' => '', + 'oxarticles__oxstockflag' => 1, + 'oxarticles__oxshopid' => 1, + 'oxarticles__oxvarstock' => -1, + 'oxarticles__oxvarcount' => 0 + ]); + + $this->assertEquals(-1, $product->getStockStatus()); + } }