Skip to content

Commit 46abe3a

Browse files
Merge pull request #261 from BitBagCommerce/op-282-bulk-actions-fix
OP-282: Bulk actions fix
2 parents 7152d14 + 51162df commit 46abe3a

5 files changed

Lines changed: 325 additions & 0 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?php
2+
3+
/*
4+
* This file has been created by developers from BitBag.
5+
* Feel free to contact us once you face any issues or want to start
6+
* You can find more information about us on https://bitbag.io and write us
7+
* an email on hello@bitbag.io.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace spec\BitBag\SyliusWishlistPlugin\Validator;
13+
14+
use BitBag\SyliusWishlistPlugin\Validator\InStockWishlistValidator;
15+
use Doctrine\Common\Collections\ArrayCollection;
16+
use PhpSpec\ObjectBehavior;
17+
use Prophecy\Argument;
18+
use stdClass;
19+
use Sylius\Bundle\CoreBundle\Validator\Constraints\CartItemAvailability;
20+
use Sylius\Bundle\OrderBundle\Controller\AddToCartCommandInterface;
21+
use Sylius\Component\Core\Model\OrderItemInterface;
22+
use Sylius\Component\Core\Model\ProductVariantInterface;
23+
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;
24+
use Sylius\Component\Order\Model\OrderInterface;
25+
use Symfony\Bundle\FrameworkBundle\Routing\Router;
26+
use Symfony\Component\HttpFoundation\Request;
27+
use Symfony\Component\HttpFoundation\RequestStack;
28+
use Symfony\Component\Validator\Constraint;
29+
use Symfony\Component\Validator\Context\ExecutionContextInterface;
30+
31+
final class InStockWishlistValidatorSpec extends ObjectBehavior
32+
{
33+
public function let(
34+
AvailabilityCheckerInterface $availabilityChecker,
35+
RequestStack $requestStack,
36+
Router $router,
37+
): void {
38+
$this->beConstructedWith($availabilityChecker, $requestStack, $router);
39+
}
40+
41+
public function it_is_initializable(): void
42+
{
43+
$this->shouldHaveType(InStockWishlistValidator::class);
44+
}
45+
46+
public function it_should_throw_exception_if_value_is_not_instance_of_command_interface(
47+
Constraint $constraint,
48+
stdClass $value,
49+
): void {
50+
$this->shouldThrow(\Exception::class)->during(
51+
'validate',
52+
[$value, $constraint],
53+
);
54+
}
55+
56+
public function it_should_throw_exception_if_constraint_is_not_instance_of_command_interface(
57+
Constraint $constraint,
58+
AddToCartCommandInterface $value,
59+
): void {
60+
$this->shouldThrow(\Exception::class)->during(
61+
'validate',
62+
[$value, $constraint],
63+
);
64+
}
65+
66+
public function it_should_throw_exception_if_request_is_null(
67+
RequestStack $requestStack,
68+
AddToCartCommandInterface $value,
69+
): void {
70+
$constraint = new CartItemAvailability();
71+
72+
$requestStack->getCurrentRequest()->willReturn(null);
73+
74+
$this->shouldThrow(\Exception::class)->during(
75+
'validate',
76+
[$value, $constraint],
77+
);
78+
}
79+
80+
public function it_should_do_nothing_if_the_request_is_not_add_to_cart(
81+
RequestStack $requestStack,
82+
AddToCartCommandInterface $value,
83+
Request $request,
84+
Router $router,
85+
): void {
86+
$constraint = new CartItemAvailability();
87+
$exampleArray = [
88+
'_route' => 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_remove_selected_products',
89+
'_controller' => 'bitbag_sylius_wishlist_plugin.controller.action.remove_selected_products_from_wishlist',
90+
'_locale' => 'en_US',
91+
'wishlistId' => '4',
92+
];
93+
94+
$requestStack->getCurrentRequest()->willReturn($request);
95+
$request->getPathInfo()->willReturn('/en_US/wishlist/4/products/delete');
96+
$router->match('/en_US/wishlist/4/products/delete')->willReturn($exampleArray);
97+
98+
$this->validate($value, $constraint)->shouldReturn(null);
99+
}
100+
101+
public function it_should_do_nothing_if_current_stock_is_not_empty(
102+
AvailabilityCheckerInterface $availabilityChecker,
103+
RequestStack $requestStack,
104+
AddToCartCommandInterface $value,
105+
Request $request,
106+
Router $router,
107+
OrderItemInterface $cartItem,
108+
ExecutionContextInterface $context,
109+
ProductVariantInterface $variant,
110+
OrderInterface $order,
111+
): void {
112+
$constraint = new CartItemAvailability();
113+
$exampleArray = ['_route' => 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_add_selected_products',
114+
'_controller' => 'bitbag_sylius_wishlist_plugin.controller.action.add_selected_products_to_cart',
115+
'_locale' => 'en_US',
116+
'wishlistId' => '4',
117+
];
118+
119+
$requestStack->getCurrentRequest()->willReturn($request);
120+
$request->getPathInfo()->willReturn('/en_US/wishlist/4/products/add');
121+
$router->match('/en_US/wishlist/4/products/add')->willReturn($exampleArray);
122+
$value->getCartItem()->willReturn($cartItem);
123+
$cartItem->getVariant()->willReturn($variant);
124+
$cartItem->getQuantity()->willReturn(5);
125+
$value->getCart()->willReturn($order);
126+
$itemsCollection = new ArrayCollection([$cartItem->getWrappedObject()]);
127+
$order->getItems()->willReturn($itemsCollection);
128+
$cartItem->equals(Argument::any())->willReturn(false);
129+
$availabilityChecker->isStockSufficient($variant, 5)->willReturn(true);
130+
131+
$context->addViolation(Argument::any(), Argument::any())->shouldNotBeCalled();
132+
133+
$this->validate($value, $constraint)->shouldReturn(null);
134+
}
135+
136+
public function it_should_do_build_violation_if_current_stock_is_empty_and_user_added_to_cart_item_from_wishlist(
137+
AvailabilityCheckerInterface $availabilityChecker,
138+
RequestStack $requestStack,
139+
AddToCartCommandInterface $value,
140+
Request $request,
141+
Router $router,
142+
OrderItemInterface $cartItem,
143+
OrderInterface $order,
144+
ProductVariantInterface $variant,
145+
ExecutionContextInterface $context,
146+
): void {
147+
$constraint = new CartItemAvailability();
148+
$constraint->message = 'sylius.cart_item.not_available';
149+
$exampleArray = ['_route' => 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_add_selected_products',
150+
'_controller' => 'bitbag_sylius_wishlist_plugin.controller.action.add_selected_products_to_cart',
151+
'_locale' => 'en_US',
152+
'wishlistId' => '4',
153+
];
154+
155+
$requestStack->getCurrentRequest()->willReturn($request);
156+
$request->getPathInfo()->willReturn('/en_US/wishlist/4/products/add');
157+
$router->match('/en_US/wishlist/4/products/add')->willReturn($exampleArray);
158+
$value->getCartItem()->willReturn($cartItem);
159+
$cartItem->getVariant()->willReturn($variant);
160+
$cartItem->getQuantity()->willReturn(5);
161+
$value->getCart()->willReturn($order);
162+
$itemsCollection = new ArrayCollection([$cartItem->getWrappedObject()]);
163+
$order->getItems()->willReturn($itemsCollection);
164+
$cartItem->equals(Argument::any())->willReturn(false);
165+
$availabilityChecker->isStockSufficient($variant, 5)->willReturn(false);
166+
$variant->getInventoryName()->willReturn('Red T-shirt');
167+
168+
$context->addViolation(
169+
$constraint->message,
170+
['%itemName%' => 'Red T-shirt'],
171+
)->shouldBeCalled();
172+
173+
$this->initialize($context);
174+
$this->validate($value, $constraint);
175+
}
176+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<container xmlns="http://symfony.com/schema/dic/services"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://symfony.com/schema/dic/services
5+
http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<service class="BitBag\SyliusWishlistPlugin\Validator\InStockWishlistValidator"
9+
id="bitbag.sylius_wishlist_plugin.validator.wishlist_in_stock_validator"
10+
decorates="sylius.validator.cart_item_availability">
11+
<argument type="service" id="sylius.availability_checker.default" />
12+
<argument type="service" id="request_stack" />
13+
<argument type="service" id="router" />
14+
<tag name="validator.constraint_validator" alias="bitbag_sylius_wishlist_plugin_validator_wishlist_in_stock_validator"/>
15+
</service>
16+
</services>
17+
18+
</container>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/services/constraint-mapping-1.0.xsd">
4+
<class name="Sylius\Bundle\OrderBundle\Controller\AddToCartCommand">
5+
<constraint name="BitBag\SyliusWishlistPlugin\Validator\Constraints\InStockWishlistConstraint">
6+
<option name="message">sylius.cart_item.not_available</option>
7+
<option name="groups">
8+
<value>wishlist_in_stock</value>
9+
</option>
10+
</constraint>
11+
</class>
12+
</constraint-mapping>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file has been created by developers from BitBag.
5+
* Feel free to contact us once you face any issues or want to start
6+
* You can find more information about us on https://bitbag.io and write us
7+
* an email on hello@bitbag.io.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace BitBag\SyliusWishlistPlugin\Validator\Constraints;
13+
14+
use Symfony\Component\Validator\Constraint;
15+
16+
final class InStockWishlistConstraint extends Constraint
17+
{
18+
public string $message = 'sylius.cart_item.not_available';
19+
20+
public const ADD_PRODUCTS_ROUTE = 'bitbag_sylius_wishlist_plugin_shop_locale_wishlist_add_selected_products';
21+
22+
public function validatedBy(): string
23+
{
24+
return 'bitbag_sylius_wishlist_plugin_validator_wishlist_in_stock_validator';
25+
}
26+
27+
public function getTargets(): string
28+
{
29+
return self::CLASS_CONSTRAINT;
30+
}
31+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
/*
4+
* This file has been created by developers from BitBag.
5+
* Feel free to contact us once you face any issues or want to start
6+
* You can find more information about us on https://bitbag.io and write us
7+
* an email on hello@bitbag.io.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace BitBag\SyliusWishlistPlugin\Validator;
13+
14+
use BitBag\SyliusWishlistPlugin\Validator\Constraints\InStockWishlistConstraint;
15+
use Sylius\Bundle\CoreBundle\Validator\Constraints\CartItemAvailability;
16+
use Sylius\Bundle\OrderBundle\Controller\AddToCartCommandInterface;
17+
use Sylius\Component\Core\Model\OrderItemInterface;
18+
use Sylius\Component\Inventory\Checker\AvailabilityCheckerInterface;
19+
use Sylius\Component\Inventory\Model\StockableInterface;
20+
use Sylius\Component\Order\Model\OrderInterface;
21+
use Symfony\Bundle\FrameworkBundle\Routing\Router;
22+
use Symfony\Component\HttpFoundation\RequestStack;
23+
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
24+
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
25+
use Symfony\Component\Validator\Constraint;
26+
use Symfony\Component\Validator\ConstraintValidator;
27+
use Webmozart\Assert\Assert;
28+
29+
final class InStockWishlistValidator extends ConstraintValidator
30+
{
31+
public function __construct(
32+
private AvailabilityCheckerInterface $availabilityChecker,
33+
private RequestStack $requestStack,
34+
private Router $router,
35+
) {
36+
}
37+
38+
public function validate(mixed $value, Constraint $constraint): void
39+
{
40+
Assert::isInstanceOf($value, AddToCartCommandInterface::class);
41+
42+
Assert::isInstanceOf($constraint, CartItemAvailability::class);
43+
44+
$request = $this->requestStack->getCurrentRequest();
45+
46+
Assert::notNull($request);
47+
48+
try {
49+
$route = $this->router->match($request->getPathInfo());
50+
} catch (ResourceNotFoundException $exception) {
51+
throw new AccessDeniedHttpException('Access denied');
52+
}
53+
54+
if (array_key_exists('_route', $route) &&
55+
InStockWishlistConstraint::ADD_PRODUCTS_ROUTE !== $route['_route']) {
56+
return;
57+
}
58+
59+
/** @var OrderItemInterface $cartItem */
60+
$cartItem = $value->getCartItem();
61+
62+
/** @var StockableInterface $variant */
63+
$variant = $cartItem->getVariant();
64+
65+
$isStockSufficient = $this->availabilityChecker->isStockSufficient(
66+
$variant,
67+
$cartItem->getQuantity() + $this->getExistingCartItemQuantityFromCart($value->getCart(), $cartItem),
68+
);
69+
70+
if (!$isStockSufficient) {
71+
$this->context->addViolation(
72+
$constraint->message,
73+
['%itemName%' => $variant->getInventoryName()],
74+
);
75+
}
76+
}
77+
78+
private function getExistingCartItemQuantityFromCart(OrderInterface $cart, OrderItemInterface $cartItem): int
79+
{
80+
foreach ($cart->getItems() as $existingCartItem) {
81+
if ($existingCartItem->equals($cartItem)) {
82+
return $existingCartItem->getQuantity();
83+
}
84+
}
85+
86+
return 0;
87+
}
88+
}

0 commit comments

Comments
 (0)