Permalink
Show file tree
Hide file tree
12 changes: 10 additions & 2 deletions
12
src/Core/Content/Product/Cart/ProductStockReachedError.php
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
NEXT-23325 - Add product line item validator for duplicate line items…
… with stock issues
- Loading branch information
1 parent
2b80089
commit 4fce120
Showing
5 changed files
with
185 additions
and
2 deletions.
There are no files selected for viewing
6 changes: 6 additions & 0 deletions
6
...g/_unreleased/2022-12-12-fix-quantity-issues-on-duplicate-product-line-items.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| title: Fix quantity issues on duplicate product line items | ||
| issue: NEXT-23325 | ||
| --- | ||
| # Core | ||
| * Added `Shopware\Core\Content\Product\Cart\ProductLineItemValidator` to check product quantity limits across product line items |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
src/Core/Content/Product/Cart/ProductLineItemValidator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| namespace Shopware\Core\Content\Product\Cart; | ||
|
|
||
| use Shopware\Core\Checkout\Cart\Cart; | ||
| use Shopware\Core\Checkout\Cart\CartValidatorInterface; | ||
| use Shopware\Core\Checkout\Cart\Error\ErrorCollection; | ||
| use Shopware\Core\Checkout\Cart\LineItem\LineItem; | ||
| use Shopware\Core\System\SalesChannel\SalesChannelContext; | ||
|
|
||
| /** | ||
| * @package checkout | ||
| */ | ||
| class ProductLineItemValidator implements CartValidatorInterface | ||
| { | ||
| public function validate(Cart $cart, ErrorCollection $errors, SalesChannelContext $context): void | ||
| { | ||
| $productLineItems = array_filter($cart->getLineItems()->getFlat(), static function (LineItem $lineItem) { | ||
| return $lineItem->getType() === LineItem::PRODUCT_LINE_ITEM_TYPE; | ||
| }); | ||
|
|
||
| foreach ($productLineItems as $lineItem) { | ||
| $productId = $lineItem->getReferencedId(); | ||
| if ($productId === null) { | ||
| continue; | ||
| } | ||
| $totalQuantity = $this->getTotalQuantity($productId, $productLineItems); | ||
|
|
||
| $quantityInformation = $lineItem->getQuantityInformation(); | ||
| if ($quantityInformation === null) { | ||
| continue; | ||
| } | ||
|
|
||
| $minPurchase = $quantityInformation->getMinPurchase(); | ||
| $available = $quantityInformation->getMaxPurchase() ?? 0; | ||
| $steps = $quantityInformation->getPurchaseSteps() ?? 1; | ||
|
|
||
| if ($available >= $totalQuantity) { | ||
| continue; | ||
| } | ||
|
|
||
| $maxAvailable = (int) (floor(($available - $minPurchase) / $steps) * $steps + $minPurchase); | ||
|
|
||
| $cart->addErrors( | ||
| new ProductStockReachedError($productId, (string) $lineItem->getLabel(), $maxAvailable, false), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param LineItem[] $productLineItems | ||
| */ | ||
| private function getTotalQuantity(string $productId, array $productLineItems): int | ||
| { | ||
| $totalQuantity = 0; | ||
| foreach ($productLineItems as $lineItem) { | ||
| if ($lineItem->getReferencedId() === $productId) { | ||
| $totalQuantity += $lineItem->getQuantity(); | ||
| } | ||
| } | ||
|
|
||
| return $totalQuantity; | ||
| } | ||
| } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
tests/unit/php/Core/Content/Product/Cart/ProductLineItemValidatorTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| namespace Shopware\Tests\Unit\Core\Content\Product\Cart; | ||
|
|
||
| use PHPUnit\Framework\TestCase; | ||
| use Shopware\Core\Checkout\Cart\Cart; | ||
| use Shopware\Core\Checkout\Cart\LineItem\QuantityInformation; | ||
| use Shopware\Core\Content\Product\Cart\ProductLineItemFactory; | ||
| use Shopware\Core\Content\Product\Cart\ProductLineItemValidator; | ||
| use Shopware\Core\Framework\Uuid\Uuid; | ||
| use Shopware\Core\System\SalesChannel\SalesChannelContext; | ||
|
|
||
| /** | ||
| * @internal | ||
| * @covers \Shopware\Core\Content\Product\Cart\ProductLineItemValidator | ||
| */ | ||
| class ProductLineItemValidatorTest extends TestCase | ||
| { | ||
| public function testValidateOnDuplicateProductsAtMaxPurchase(): void | ||
| { | ||
| $cart = new Cart(Uuid::randomHex(), Uuid::randomHex()); | ||
| $builder = new ProductLineItemFactory(); | ||
| $cart->add( | ||
| $builder | ||
| ->create('product-1') | ||
| ->setQuantityInformation( | ||
| (new QuantityInformation()) | ||
| ->setMinPurchase(1) | ||
| ->setMaxPurchase(1) | ||
| ->setPurchaseSteps(1) | ||
| ) | ||
| ); | ||
| $cart->add( | ||
| $builder | ||
| ->create('product-2') | ||
| ->setReferencedId('product-1') | ||
| ->setQuantityInformation( | ||
| (new QuantityInformation()) | ||
| ->setMinPurchase(1) | ||
| ->setMaxPurchase(1) | ||
| ->setPurchaseSteps(1) | ||
| ) | ||
| ); | ||
|
|
||
| static::assertCount(0, $cart->getErrors()); | ||
|
|
||
| $validator = new ProductLineItemValidator(); | ||
| $validator->validate($cart, $cart->getErrors(), $this->createMock(SalesChannelContext::class)); | ||
|
|
||
| static::assertCount(1, $cart->getErrors()); | ||
| } | ||
|
|
||
| public function testValidateOnDuplicateProductsWithSafeQuantity(): void | ||
| { | ||
| $cart = new Cart(Uuid::randomHex(), Uuid::randomHex()); | ||
| $builder = new ProductLineItemFactory(); | ||
| $cart->add( | ||
| $builder | ||
| ->create('product-1') | ||
| ->setQuantityInformation( | ||
| (new QuantityInformation()) | ||
| ->setMinPurchase(1) | ||
| ->setMaxPurchase(3) | ||
| ->setPurchaseSteps(1) | ||
| ) | ||
| ); | ||
| $cart->add( | ||
| $builder | ||
| ->create('product-2') | ||
| ->setReferencedId('product-1') | ||
| ->setQuantityInformation( | ||
| (new QuantityInformation()) | ||
| ->setMinPurchase(1) | ||
| ->setMaxPurchase(3) | ||
| ->setPurchaseSteps(1) | ||
| ) | ||
| ); | ||
|
|
||
| static::assertCount(0, $cart->getErrors()); | ||
|
|
||
| $validator = new ProductLineItemValidator(); | ||
| $validator->validate($cart, $cart->getErrors(), $this->createMock(SalesChannelContext::class)); | ||
|
|
||
| static::assertCount(0, $cart->getErrors()); | ||
| } | ||
|
|
||
| public function testValidateOnDuplicateProductsWithoutQuantityInformation(): void | ||
| { | ||
| $cart = new Cart(Uuid::randomHex(), Uuid::randomHex()); | ||
| $builder = new ProductLineItemFactory(); | ||
| $cart->add($builder->create('product-1')); | ||
| $cart->add($builder->create('product-2')->setReferencedId('product-1')); | ||
|
|
||
| static::assertCount(0, $cart->getErrors()); | ||
|
|
||
| $validator = new ProductLineItemValidator(); | ||
| $validator->validate($cart, $cart->getErrors(), $this->createMock(SalesChannelContext::class)); | ||
|
|
||
| static::assertCount(0, $cart->getErrors()); | ||
| } | ||
| } |